actor: APPC
recipe: Restart
target:
- type: VM
+ targetType: VM
retry: 3
timeout: 1200
success: final_success
"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",
"failure_exception": "final_failure_exception",
"failure_guard": "final_failure_guard"
}
- ]
+ ],
+ "controllerName": "usecases"
}
}
}
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
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
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
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
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:
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:
--- /dev/null
+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
+
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
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:
--- /dev/null
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ Copyright (C) 2018 Huawei Intellectual Property. All rights reserved.
+ Modifications Copyright (C) 2019-2020 Nordix Foundation.
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>model-actors</artifactId>
+ <version>2.2.1-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>actor.aai</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actorServiceProvider</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>aai</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>events</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>aai</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.common</groupId>
+ <artifactId>policy-endpoints</artifactId>
+ <version>${policy.common.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId>
+ <artifactId>actor.test</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+ <artifactId>simulators</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+/*-
+ * ============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));
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A&AI Custom Query. Stores the {@link AaiCqResponse} in the context. In addition, if the
+ * context does not contain the "tenant" data for the vserver, then it will request that,
+ * as well.
+ */
+public class AaiCustomQueryOperation extends HttpOperation<String> {
+ private static final Logger logger = LoggerFactory.getLogger(AaiCustomQueryOperation.class);
+
+ public static final String NAME = "CustomQuery";
+
+ public static final String RESOURCE_LINK = "resource-link";
+ public static final String RESULT_DATA = "result-data";
+
+ private static final String PREFIX = "/aai/v16";
+
+ /**
+ * Constructs the object.
+ *
+ * @param params operation parameters
+ * @param operator operator that created this operation
+ */
+ public AaiCustomQueryOperation(ControlLoopOperationParams params, HttpOperator operator) {
+ super(params, operator, String.class);
+ }
+
+ /**
+ * Queries the vserver, if necessary.
+ */
+ @Override
+ protected CompletableFuture<OperationOutcome> startPreprocessorAsync() {
+ String vserver = params.getTargetEntity();
+
+ ControlLoopOperationParams tenantParams = params.toBuilder().actor(AaiConstants.ACTOR_NAME)
+ .operation(AaiGetOperation.TENANT).payload(null).retry(null).timeoutSec(null).build();
+
+ return params.getContext().obtain(AaiGetOperation.getTenantKey(vserver), tenantParams);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ Map<String, String> request = makeRequest();
+
+ Entity<Map<String, String>> entity = Entity.entity(request, MediaType.APPLICATION_JSON);
+
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logRestRequest(url, request);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().put(callback, makePath(), entity, headers));
+ // @formatter:on
+ }
+
+ /**
+ * Constructs the custom query using the previously retrieved tenant data.
+ */
+ private Map<String, String> makeRequest() {
+ String vserver = params.getTargetEntity();
+ StandardCoderObject tenant = params.getContext().getProperty(AaiGetOperation.getTenantKey(vserver));
+
+ String resourceLink = tenant.getString(RESULT_DATA, 0, RESOURCE_LINK);
+ if (resourceLink == null) {
+ throw new IllegalArgumentException("cannot perform custom query - no resource-link");
+ }
+
+ resourceLink = resourceLink.replace(PREFIX, "");
+
+ return Map.of("start", resourceLink, "query", "query/closed-loop");
+ }
+
+ @Override
+ protected Map<String, Object> makeHeaders() {
+ return AaiUtil.makeHeaders(params);
+ }
+
+ /**
+ * Injects the response into the context.
+ */
+ @Override
+ protected void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse, String response) {
+
+ logger.info("{}: caching response for {}", getFullName(), params.getRequestId());
+ params.getContext().setProperty(AaiCqResponse.CONTEXT_KEY, new AaiCqResponse(response));
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Superclass of A&AI operators that use "get" to perform their request and store their
+ * response within the context as a {@link StandardCoderObject}. The property name under
+ * which they are stored is ${actor}.${operation}.${targetEntity}.
+ */
+public class AaiGetOperation extends HttpOperation<StandardCoderObject> {
+ private static final Logger logger = LoggerFactory.getLogger(AaiGetOperation.class);
+
+ public static final int DEFAULT_RETRY = 3;
+
+ // operation names
+ public static final String TENANT = "Tenant";
+
+ // property prefixes
+ private static final String TENANT_KEY_PREFIX = AaiConstants.CONTEXT_PREFIX + TENANT + ".";
+
+ /**
+ * Operation names supported by this operator.
+ */
+ public static final Set<String> OPERATIONS = Set.of(TENANT);
+
+
+ /**
+ * Responses that are retrieved from A&AI are placed in the operation context under
+ * the name "${propertyPrefix}.${targetEntity}".
+ */
+ private final String propertyPrefix;
+
+ /**
+ * Constructs the object.
+ *
+ * @param params operation parameters
+ * @param operator operator that created this operation
+ */
+ public AaiGetOperation(ControlLoopOperationParams params, HttpOperator operator) {
+ super(params, operator, StandardCoderObject.class);
+ this.propertyPrefix = operator.getFullName() + ".";
+ }
+
+ /**
+ * Gets the "context key" for the tenant query response associated with the given
+ * target entity.
+ *
+ * @param targetEntity target entity
+ * @return the "context key" for the response associated with the given target
+ */
+ public static String getTenantKey(String targetEntity) {
+ return (TENANT_KEY_PREFIX + targetEntity);
+ }
+
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ Map<String, Object> headers = makeHeaders();
+
+ headers.put("Accept", MediaType.APPLICATION_JSON);
+ String url = makeUrl();
+
+ logRestRequest(url, null);
+
+ // @formatter:off
+ return handleResponse(outcome, url,
+ callback -> operator.getClient().get(callback, makePath(), headers));
+ // @formatter:on
+ }
+
+ @Override
+ protected Map<String, Object> makeHeaders() {
+ return AaiUtil.makeHeaders(params);
+ }
+
+ @Override
+ public String makePath() {
+ return (operator.getPath() + "/" + params.getTargetEntity());
+ }
+
+ /**
+ * Injects the response into the context.
+ */
+ @Override
+ protected void postProcessResponse(OperationOutcome outcome, String url, Response rawResponse,
+ StandardCoderObject response) {
+ String entity = params.getTargetEntity();
+
+ logger.info("{}: caching response of {} for {}", getFullName(), entity, params.getRequestId());
+
+ params.getContext().setProperty(propertyPrefix + entity, response);
+ }
+
+ /**
+ * Provides a default retry value, if none specified.
+ */
+ @Override
+ protected int getRetry(Integer retry) {
+ return (retry == null ? DEFAULT_RETRY : retry);
+ }
+}
--- /dev/null
+/*-
+ * ============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;
+ }
+}
--- /dev/null
+org.onap.policy.controlloop.actor.aai.AaiActorServiceProvider
--- /dev/null
+/*-
+ * ============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());
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.aai.AaiCqResponse;
+import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams;
+import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class AaiCustomQueryOperationTest extends BasicAaiOperator<Map<String, String>> {
+ private static final StandardCoder coder = new StandardCoder();
+
+ private static final String MY_LINK = "my-link";
+
+ @Mock
+ private Actor tenantActor;
+
+ private AaiCustomQueryOperation oper;
+
+ public AaiCustomQueryOperationTest() {
+ super(AaiConstants.ACTOR_NAME, AaiCustomQueryOperation.NAME);
+ }
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ MyTenantOperator tenantOperator = new MyTenantOperator();
+
+ when(service.getActor(AaiConstants.ACTOR_NAME)).thenReturn(tenantActor);
+ when(tenantActor.getOperator(AaiGetOperation.TENANT)).thenReturn(tenantOperator);
+
+ oper = new AaiCustomQueryOperation(params, operator);
+ }
+
+ @Test
+ public void testAaiCustomQueryOperation() {
+ assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName());
+ assertEquals(AaiCustomQueryOperation.NAME, oper.getName());
+ }
+
+ @Test
+ public void testStartOperationAsync_testStartPreprocessorAsync_testMakeRequest_testPostProcess() throws Exception {
+ // need two responses
+ when(rawResponse.readEntity(String.class)).thenReturn(makeTenantReply()).thenReturn(makeCqReply());
+ when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+ when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+ CompletableFuture<OperationOutcome> future2 = oper.start();
+
+ assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+ // tenant response should have been cached within the context
+ assertNotNull(context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+
+ // custom query response should have been cached within the context
+ AaiCqResponse cqData = context.getProperty(AaiCqResponse.CONTEXT_KEY);
+ assertNotNull(cqData);
+ }
+
+ /**
+ * Tests when preprocessor step is not needed.
+ */
+ @Test
+ public void testStartOperationAsync_testStartPreprocessorAsyncNotNeeded() throws Exception {
+ // pre-load the tenant data
+ final StandardCoderObject data = preloadTenantData();
+
+ // only need one response
+ when(rawResponse.readEntity(String.class)).thenReturn(makeCqReply());
+ when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+ CompletableFuture<OperationOutcome> future2 = oper.start();
+
+ assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+ // should not have replaced tenant response
+ assertSame(data, context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+
+ // custom query response should have been cached within the context
+ AaiCqResponse cqData = context.getProperty(AaiCqResponse.CONTEXT_KEY);
+ assertNotNull(cqData);
+ }
+
+ @Test
+ public void testMakeHeaders() {
+ verifyHeaders(oper.makeHeaders());
+ }
+
+ @Test
+ public void testMakeRequestNoResourceLink() throws Exception {
+ // pre-load EMPTY tenant data
+ preloadTenantData(new StandardCoderObject());
+
+ when(rawResponse.readEntity(String.class)).thenReturn(makeCqReply());
+ when(client.put(any(), any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+ CompletableFuture<OperationOutcome> future2 = oper.start();
+
+ assertEquals(PolicyResult.FAILURE_EXCEPTION, future2.get(5, TimeUnit.SECONDS).getResult());
+ }
+
+ private String makeTenantReply() throws Exception {
+ Map<String, String> links = Map.of(AaiCustomQueryOperation.RESOURCE_LINK, MY_LINK);
+ List<Map<String, String>> data = Arrays.asList(links);
+
+ Map<String, Object> reply = Map.of(AaiCustomQueryOperation.RESULT_DATA, data);
+ return coder.encode(reply);
+ }
+
+ private String makeCqReply() {
+ return "{}";
+ }
+
+ private StandardCoderObject preloadTenantData() throws Exception {
+ StandardCoderObject data = coder.decode(makeTenantReply(), StandardCoderObject.class);
+ preloadTenantData(data);
+ return data;
+ }
+
+ private void preloadTenantData(StandardCoderObject data) {
+ context.setProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY), data);
+ }
+
+ protected class MyTenantOperator extends HttpOperator {
+ public MyTenantOperator() {
+ super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+
+ HttpParams http = HttpParams.builder().clientName(MY_CLIENT).path(PATH).build();
+
+ configure(Util.translateToMap(AaiGetOperation.TENANT, http));
+ start();
+ }
+
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return new AaiGetOperation(params, this);
+ }
+
+ @Override
+ protected HttpClientFactory getClientFactory() {
+ return factory;
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actor.aai;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.aai.AaiConstants;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class AaiGetOperatorTest extends BasicAaiOperator<Void> {
+
+ private static final String INPUT_FIELD = "input";
+ private static final String TEXT = "my-text";
+
+ private AaiGetOperation oper;
+
+ public AaiGetOperatorTest() {
+ super(AaiConstants.ACTOR_NAME, AaiGetOperation.TENANT);
+ }
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ oper = new AaiGetOperation(params, operator);
+ }
+
+ @Test
+ public void testGetRetry() {
+ // use default if null retry
+ assertEquals(AaiGetOperation.DEFAULT_RETRY, oper.getRetry(null));
+
+ // otherwise, use specified value
+ assertEquals(0, oper.getRetry(0));
+ assertEquals(10, oper.getRetry(10));
+ }
+
+ @Test
+ public void testStartOperationAsync_testStartQueryAsync_testPostProcessResponse() throws Exception {
+
+ // return a map in the reply
+ Map<String, String> reply = Map.of(INPUT_FIELD, TEXT);
+ when(rawResponse.readEntity(String.class)).thenReturn(new StandardCoder().encode(reply));
+
+ when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+ CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome);
+ assertFalse(future2.isDone());
+
+ assertEquals(PolicyResult.SUCCESS, future2.get(5, TimeUnit.SECONDS).getResult());
+
+ // data should have been cached within the context
+ StandardCoderObject data = context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY));
+ assertNotNull(data);
+ assertEquals(TEXT, data.getString(INPUT_FIELD));
+ }
+
+ /**
+ * Tests startOperationAsync() when there's a failure.
+ */
+ @Test
+ public void testStartOperationAsyncFailure() throws Exception {
+
+ when(rawResponse.getStatus()).thenReturn(500);
+ when(rawResponse.readEntity(String.class)).thenReturn("");
+
+ when(client.get(any(), any(), any())).thenAnswer(provideResponse(rawResponse));
+
+ CompletableFuture<OperationOutcome> future2 = oper.startOperationAsync(1, outcome);
+ assertFalse(future2.isDone());
+
+ assertEquals(PolicyResult.FAILURE, future2.get(5, TimeUnit.SECONDS).getResult());
+
+ // data should NOT have been cached within the context
+ assertNull(context.getProperty(AaiGetOperation.getTenantKey(TARGET_ENTITY)));
+ }
+
+ @Test
+ public void testMakeHeaders() {
+ verifyHeaders(oper.makeHeaders());
+ }
+
+ @Test
+ public void testMakePath() {
+ assertEquals(PATH + "/" + TARGET_ENTITY, oper.makePath());
+ }
+
+ @Test
+ public void testAaiGetOperator() {
+ assertEquals(AaiConstants.ACTOR_NAME, oper.getActorName());
+ assertEquals(AaiGetOperation.TENANT, oper.getName());
+ }
+
+ @Test
+ public void testGetTenantKey() {
+ assertEquals("AAI.Tenant." + TARGET_ENTITY, AaiGetOperation.getTenantKey(TARGET_ENTITY));
+ }
+}
--- /dev/null
+/*-
+ * ============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);
+ }
+}
--- /dev/null
+/*-
+ * ============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"));
+ }
+}
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import org.onap.policy.common.endpoints.http.client.HttpClient;
import org.onap.policy.common.endpoints.http.client.HttpClientFactory;
import org.onap.policy.controlloop.VirtualControlLoopEvent;
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);
+ };
+ }
}
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;
}
@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());
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());
+ }
}
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");
}
}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairActorParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPairManager;
+
+/**
+ * Actor that uses a topic pair. The actor's parameters must be a
+ * {@link TopicPairActorParams}.
+ */
+public class TopicPairActor extends ActorImpl implements TopicPairManager {
+
+ /**
+ * Maps a topic source and target name to its topic pair.
+ */
+ private final Map<Pair<String, String>, TopicPair> params2topic = new ConcurrentHashMap<>();
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param name actor's name
+ */
+ public TopicPairActor(String name) {
+ super(name);
+ }
+
+ @Override
+ protected void doStart() {
+ params2topic.values().forEach(TopicPair::start);
+ super.doStart();
+ }
+
+ @Override
+ protected void doStop() {
+ params2topic.values().forEach(TopicPair::stop);
+ super.doStop();
+ }
+
+ @Override
+ protected void doShutdown() {
+ params2topic.values().forEach(TopicPair::shutdown);
+ params2topic.clear();
+ super.doShutdown();
+ }
+
+ @Override
+ public TopicPair getTopicPair(String source, String target) {
+ Pair<String, String> key = Pair.of(source, target);
+ return params2topic.computeIfAbsent(key, pair -> new TopicPair(source, target));
+ }
+
+ /**
+ * Translates the parameters to a {@link TopicPairActorParams} and then creates a
+ * function that will extract operator-specific parameters.
+ */
+ @Override
+ protected Function<String, Map<String, Object>> makeOperatorParameters(Map<String, Object> actorParameters) {
+ String actorName = getName();
+
+ TopicPairActorParams params = Util.translate(actorName, actorParameters, TopicPairActorParams.class);
+ ValidationResult result = params.validate(getName());
+ if (!result.isValid()) {
+ throw new ParameterValidationRuntimeException("invalid parameters", result);
+ }
+
+ // create a map of the default parameters
+ Map<String, Object> defaultParams = Util.translateToMap(getName(), params.getDefaults());
+ Map<String, Map<String, Object>> operations = params.getOperation();
+
+ return operationName -> {
+ Map<String, Object> specificParams = operations.get(operationName);
+ if (specificParams == null) {
+ return null;
+ }
+
+ // start with a copy of defaults and overlay with specific
+ Map<String, Object> subparams = new TreeMap<>(defaultParams);
+ subparams.putAll(specificParams);
+
+ return Util.translateToMap(getName() + "." + operationName, subparams);
+ };
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import org.apache.commons.lang3.tuple.Triple;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Operation that uses a Topic pair.
+ *
+ * @param <S> response type
+ */
+@Getter
+public abstract class TopicPairOperation<Q, S> extends OperationPartial {
+ private static final Logger logger = LoggerFactory.getLogger(TopicPairOperation.class);
+ private static final Coder coder = new StandardCoder();
+
+ // fields extracted from the operator
+
+ private final TopicPair topicPair;
+ private final Forwarder forwarder;
+ private final TopicPairParams pairParams;
+ private final long timeoutMs;
+
+ /**
+ * Response class.
+ */
+ private final Class<S> responseClass;
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param params operation parameters
+ * @param operator operator that created this operation
+ * @param clazz response class
+ */
+ public TopicPairOperation(ControlLoopOperationParams params, TopicPairOperator operator, Class<S> clazz) {
+ super(params, operator);
+ this.topicPair = operator.getTopicPair();
+ this.forwarder = operator.getForwarder();
+ this.pairParams = operator.getParams();
+ this.responseClass = clazz;
+ this.timeoutMs = TimeUnit.MILLISECONDS.convert(pairParams.getTimeoutSec(), TimeUnit.SECONDS);
+ }
+
+ /**
+ * If no timeout is specified, then it returns the default timeout.
+ */
+ @Override
+ protected long getTimeoutMs(Integer timeoutSec) {
+ // TODO move this method to the superclass
+ return (timeoutSec == null || timeoutSec == 0 ? this.timeoutMs : super.getTimeoutMs(timeoutSec));
+ }
+
+ /**
+ * Publishes the request and arranges to receive the response.
+ */
+ @Override
+ protected CompletableFuture<OperationOutcome> startOperationAsync(int attempt, OperationOutcome outcome) {
+
+ final Q request = makeRequest(attempt);
+ final List<String> expectedKeyValues = getExpectedKeyValues(attempt, request);
+
+ final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>();
+ final CompletableFuture<Triple<CommInfrastructure, String, StandardCoderObject>> future =
+ new CompletableFuture<>();
+ final Executor executor = params.getExecutor();
+
+ // register a listener BEFORE publishing
+
+ // @formatter:off
+ TriConsumer<CommInfrastructure, String, StandardCoderObject> listener =
+ (infra, rawResponse, scoResponse) -> future.complete(Triple.of(infra, rawResponse, scoResponse));
+ // @formatter:on
+
+ // TODO this currently only allows a single matching response
+
+ forwarder.register(expectedKeyValues, listener);
+
+ // ensure listener is unregistered if the controller is canceled
+ controller.add(() -> forwarder.unregister(expectedKeyValues, listener));
+
+ // publish the request
+ try {
+ publishRequest(request);
+ } catch (RuntimeException e) {
+ logger.warn("{}: failed to publish request for {}", getFullName(), params.getRequestId());
+ forwarder.unregister(expectedKeyValues, listener);
+ throw e;
+ }
+
+
+ // once "future" completes, process the response, and then complete the controller
+
+ // @formatter:off
+ future.thenApplyAsync(
+ triple -> processResponse(triple.getLeft(), outcome, triple.getMiddle(), triple.getRight()),
+ executor)
+ .whenCompleteAsync(controller.delayedComplete(), executor);
+ // @formatter:on
+
+ return controller;
+ }
+
+ /**
+ * Makes the request.
+ *
+ * @param attempt operation attempt
+ * @return a new request
+ */
+ protected abstract Q makeRequest(int attempt);
+
+ /**
+ * Gets values, expected in the response, that should match the selector keys.
+ *
+ * @param attempt operation attempt
+ * @param request request to be published
+ * @return a list of the values to be matched by the selector keys
+ */
+ protected abstract List<String> getExpectedKeyValues(int attempt, Q request);
+
+ /**
+ * Publishes the request. Encodes the request, if it is not already a String.
+ *
+ * @param request request to be published
+ */
+ protected void publishRequest(Q request) {
+ String json;
+ try {
+ if (request instanceof String) {
+ json = request.toString();
+ } else {
+ json = makeCoder().encode(request);
+ }
+ } catch (CoderException e) {
+ throw new IllegalArgumentException("cannot encode request", e);
+ }
+
+ List<CommInfrastructure> list = topicPair.publish(json);
+ if (list.isEmpty()) {
+ throw new IllegalStateException("nothing published");
+ }
+
+ logTopicRequest(list, request);
+ }
+
+ /**
+ * Processes a response.
+ *
+ * @param infra communication infrastructure on which the response was received
+ * @param outcome outcome to be populated
+ * @param response raw response to process
+ * @param scoResponse response, as a {@link StandardCoderObject}
+ * @return the outcome
+ */
+ protected OperationOutcome processResponse(CommInfrastructure infra, OperationOutcome outcome, String rawResponse,
+ StandardCoderObject scoResponse) {
+
+ logger.info("{}.{}: response received for {}", params.getActor(), params.getOperation(), params.getRequestId());
+
+ logTopicResponse(infra, rawResponse);
+
+ S response;
+ if (responseClass == String.class) {
+ response = responseClass.cast(rawResponse);
+
+ } else if (responseClass == StandardCoderObject.class) {
+ response = responseClass.cast(scoResponse);
+
+ } else {
+ try {
+ response = makeCoder().decode(rawResponse, responseClass);
+ } catch (CoderException e) {
+ logger.warn("{}.{} cannot decode response for {}", params.getActor(), params.getOperation(),
+ params.getRequestId());
+ throw new IllegalArgumentException("cannot decode response", e);
+ }
+ }
+
+ if (!isSuccess(rawResponse, response)) {
+ logger.info("{}.{} request failed for {}", params.getActor(), params.getOperation(),
+ params.getRequestId());
+ return setOutcome(outcome, PolicyResult.FAILURE);
+ }
+
+ logger.info("{}.{} request succeeded for {}", params.getActor(), params.getOperation(), params.getRequestId());
+ setOutcome(outcome, PolicyResult.SUCCESS);
+ postProcessResponse(outcome, rawResponse, response);
+
+ return outcome;
+ }
+
+ /**
+ * Processes a successful response.
+ *
+ * @param outcome outcome to be populated
+ * @param rawResponse raw response
+ * @param response decoded response
+ */
+ protected void postProcessResponse(OperationOutcome outcome, String rawResponse, S response) {
+ // do nothing
+ }
+
+ /**
+ * Determines if the response indicates success.
+ *
+ * @param rawResponse raw response
+ * @param response decoded response
+ * @return {@code true} if the response indicates success, {@code false} otherwise
+ */
+ protected abstract boolean isSuccess(String rawResponse, S response);
+
+ /**
+ * Logs a TOPIC request. If the request is not of type, String, then it attempts to
+ * pretty-print it into JSON before logging.
+ *
+ * @param infrastructures list of communication infrastructures on which it was
+ * published
+ * @param request request to be logged
+ */
+ protected void logTopicRequest(List<CommInfrastructure> infrastructures, Q request) {
+ if (infrastructures.isEmpty()) {
+ return;
+ }
+
+ String json;
+ try {
+ if (request == null) {
+ json = null;
+ } else if (request instanceof String) {
+ json = request.toString();
+ } else {
+ json = makeCoder().encode(request, true);
+ }
+
+ } catch (CoderException e) {
+ logger.warn("cannot pretty-print request", e);
+ json = request.toString();
+ }
+
+ for (CommInfrastructure infra : infrastructures) {
+ logger.info("[OUT|{}|{}|]{}{}", infra, pairParams.getTarget(), NetLoggerUtil.SYSTEM_LS, json);
+ }
+ }
+
+ /**
+ * Logs a TOPIC response. If the response is not of type, String, then it attempts to
+ * pretty-print it into JSON before logging.
+ *
+ * @param infra communication infrastructure on which the response was received
+ * @param response response to be logged
+ */
+ protected <T> void logTopicResponse(CommInfrastructure infra, T response) {
+ String json;
+ try {
+ if (response == null) {
+ json = null;
+ } else if (response instanceof String) {
+ json = response.toString();
+ } else {
+ json = makeCoder().encode(response, true);
+ }
+
+ } catch (CoderException e) {
+ logger.warn("cannot pretty-print response", e);
+ json = response.toString();
+ }
+
+ logger.info("[IN|{}|{}|]{}{}", infra, pairParams.getSource(), NetLoggerUtil.SYSTEM_LS, json);
+ }
+
+ // these may be overridden by junit tests
+
+ protected Coder makeCoder() {
+ return coder;
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import lombok.Getter;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPairManager;
+
+/**
+ * Operator that uses a pair of topics, one for publishing the request, and another for
+ * receiving the response. Topic operators may share a {@link TopicPair}.
+ */
+public abstract class TopicPairOperator extends OperatorPartial {
+
+ /**
+ * Manager from which to get the topic pair.
+ */
+ private final TopicPairManager pairManager;
+
+ /**
+ * Keys used to extract the fields used to select responses for this operator.
+ */
+ private final List<SelectorKey> selectorKeys;
+
+ /*
+ * The remaining fields are initialized when configure() is invoked, thus they may
+ * change.
+ */
+
+ /**
+ * Current parameters. While {@link params} may change, the values contained within it
+ * will not, thus operations may copy it.
+ */
+ @Getter
+ private TopicPairParams params;
+
+ /**
+ * Topic pair associated with the parameters.
+ */
+ @Getter
+ private TopicPair topicPair;
+
+ /**
+ * Forwarder associated with the parameters.
+ */
+ @Getter
+ private Forwarder forwarder;
+
+
+ /**
+ * Constructs the object.
+ *
+ * @param actorName name of the actor with which this operator is associated
+ * @param name operation name
+ * @param pairManager manager from which to get the topic pair
+ * @param selectorKeys keys used to extract the fields used to select responses for
+ * this operator
+ */
+ public TopicPairOperator(String actorName, String name, TopicPairManager pairManager,
+ List<SelectorKey> selectorKeys) {
+ super(actorName, name);
+ this.pairManager = pairManager;
+ this.selectorKeys = selectorKeys;
+ }
+
+ @Override
+ protected void doConfigure(Map<String, Object> parameters) {
+ params = Util.translate(getFullName(), parameters, TopicPairParams.class);
+ ValidationResult result = params.validate(getFullName());
+ if (!result.isValid()) {
+ throw new ParameterValidationRuntimeException("invalid parameters", result);
+ }
+
+ topicPair = pairManager.getTopicPair(params.getSource(), params.getTarget());
+ forwarder = topicPair.addForwarder(selectorKeys);
+ }
+
+ /**
+ * Makes an operator that will construct operations.
+ *
+ * @param <Q> request type
+ * @param <S> response type
+ * @param actorName actor name
+ * @param operation operation name
+ * @param pairManager manager from which to get the topic pair
+ * @param operationMaker function to make an operation
+ * @param keys keys used to extract the fields used to select responses for this
+ * operator
+ * @return a new operator
+ */
+ // @formatter:off
+ public static <Q,S> TopicPairOperator makeOperator(String actorName, String operation, TopicPairManager pairManager,
+ BiFunction<ControlLoopOperationParams, TopicPairOperator, TopicPairOperation<Q,S>> operationMaker,
+ SelectorKey... keys) {
+ // @formatter:off
+
+ return makeOperator(actorName, operation, pairManager, Arrays.asList(keys), operationMaker);
+ }
+
+ /**
+ * Makes an operator that will construct operations.
+ *
+ * @param <Q> request type
+ * @param <S> response type
+ * @param actorName actor name
+ * @param operation operation name
+ * @param pairManager manager from which to get the topic pair
+ * @param keys keys used to extract the fields used to select responses for
+ * this operator
+ * @param operationMaker function to make an operation
+ * @return a new operator
+ */
+ // @formatter:off
+ public static <Q,S> TopicPairOperator makeOperator(String actorName, String operation, TopicPairManager pairManager,
+ List<SelectorKey> keys,
+ BiFunction<ControlLoopOperationParams, TopicPairOperator, TopicPairOperation<Q,S>> operationMaker) {
+ // @formatter:on
+
+ return new TopicPairOperator(actorName, operation, pairManager, keys) {
+ @Override
+ public synchronized Operation buildOperation(ControlLoopOperationParams params) {
+ return operationMaker.apply(params, this);
+ }
+ };
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import java.util.Map;
+import lombok.Builder;
+import lombok.Data;
+import org.onap.policy.common.parameters.BeanValidationResult;
+import org.onap.policy.common.parameters.BeanValidator;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.annotations.NotBlank;
+import org.onap.policy.common.parameters.annotations.NotNull;
+
+/**
+ * Parameters used by Actors whose Operators use a pair of Topics, one to publish requests
+ * and the other to receive responses.
+ */
+@NotNull
+@NotBlank
+@Data
+@Builder
+public class TopicPairActorParams {
+
+ /**
+ * This contains the default parameters that are used when an operation doesn't
+ * specify them. Note: each operation to be used must still have an entry in
+ * {@link #operation}, even if it's empty. Otherwise, the given operation will not be
+ * started.
+ */
+ private TopicPairParams defaults;
+
+ /**
+ * Maps an operation name to its individual parameters.
+ */
+ private Map<String, Map<String, Object>> operation;
+
+
+ /**
+ * Validates the parameters.
+ *
+ * @param name name of the object containing these parameters
+ * @return "this"
+ * @throws IllegalArgumentException if the parameters are invalid
+ */
+ public TopicPairActorParams doValidation(String name) {
+ ValidationResult result = validate(name);
+ if (!result.isValid()) {
+ throw new ParameterValidationRuntimeException("invalid parameters", result);
+ }
+
+ return this;
+ }
+
+ /**
+ * Validates the parameters.
+ *
+ * @param resultName name of the result
+ *
+ * @return the validation result
+ */
+ public ValidationResult validate(String resultName) {
+ BeanValidationResult result = new BeanValidator().validateTop(resultName, this);
+
+ if (defaults != null) {
+ result.addResult(defaults.validate("defaults"));
+ }
+
+ // @formatter:off
+ result.validateMap("operation", operation,
+ (result2, entry) -> result2.validateNotNull(entry.getKey(), entry.getValue()));
+ // @formatter:on
+
+ return result;
+ }
+}
import org.onap.policy.common.parameters.annotations.NotNull;
/**
- * Parameters used by Operators that connect to a server via DMaaP.
+ * Parameters used by Operators that use a pair of Topics, one to publish requests and the
+ * other to receive responses.
*/
@NotNull
@NotBlank
@Data
@Builder(toBuilder = true)
-public class TopicParams {
+public class TopicPairParams {
/**
- * Name of the target topic end point to which requests should be published.
+ * Source topic end point, from which to read responses.
*/
- private String target;
+ private String source;
/**
- * Source topic end point, from which to read responses.
+ * Name of the target topic end point to which requests should be published.
*/
- private String source;
+ private String target;
/**
- * Amount of time, in seconds to wait for the response, where zero indicates that it
- * should wait forever. The default is zero.
+ * Amount of time, in seconds to wait for the response. The default is five minutes.
*/
- @Min(0)
+ @Min(1)
@Builder.Default
- private long timeoutSec = 0;
+ private int timeoutSec = 300;
/**
- * Validates both the publisher and the subscriber parameters.
+ * Validates the parameters.
*
* @param resultName name of the result
*
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Forwarder that selectively forwards message to listeners based on the content of the
+ * message. Each forwarder is associated with a single set of selector keys. Listeners are
+ * then registered with that forwarder for a particular set of values for the given keys.
+ */
+public class Forwarder {
+ private static final Logger logger = LoggerFactory.getLogger(Forwarder.class);
+
+ /**
+ * Maps a set of field values to one or more listeners.
+ */
+ // @formatter:off
+ private final Map<List<String>, Map<TriConsumer<CommInfrastructure, String, StandardCoderObject>, String>>
+ values2listeners = new ConcurrentHashMap<>();
+ // @formatter:on
+
+ /**
+ * Keys used to extract the field values from the {@link StandardCoderObject}.
+ */
+ private final List<SelectorKey> keys;
+
+ /**
+ * Constructs the object.
+ *
+ * @param keys keys used to extract the field's value from the
+ * {@link StandardCoderObject}
+ */
+ public Forwarder(List<SelectorKey> keys) {
+ this.keys = keys;
+ }
+
+ /**
+ * Registers a listener for messages containing the given field values.
+ *
+ * @param values field values of interest, in one-to-one correspondence with the keys
+ * @param listener listener to register
+ */
+ public void register(List<String> values, TriConsumer<CommInfrastructure, String, StandardCoderObject> listener) {
+ if (keys.size() != values.size()) {
+ throw new IllegalArgumentException("key/value mismatch");
+ }
+
+ values2listeners.compute(values, (key, listeners) -> {
+ Map<TriConsumer<CommInfrastructure, String, StandardCoderObject>, String> map = listeners;
+ if (map == null) {
+ map = new ConcurrentHashMap<>();
+ }
+
+ map.put(listener, "");
+ return map;
+ });
+ }
+
+ /**
+ * Unregisters a listener for messages containing the given field values.
+ *
+ * @param values field values of interest, in one-to-one correspondence with the keys
+ * @param listener listener to unregister
+ */
+ public void unregister(List<String> values, TriConsumer<CommInfrastructure, String, StandardCoderObject> listener) {
+ values2listeners.computeIfPresent(values, (key, listeners) -> {
+ listeners.remove(listener);
+ return (listeners.isEmpty() ? null : listeners);
+ });
+ }
+
+ /**
+ * Processes a message, forwarding it to the appropriate listeners, if any.
+ *
+ * @param infra communication infrastructure on which the response was received
+ * @param textMessage original text message that was received
+ * @param scoMessage decoded text message
+ */
+ public void onMessage(CommInfrastructure infra, String textMessage, StandardCoderObject scoMessage) {
+ // extract the key values from the message
+ List<String> values = new ArrayList<>(keys.size());
+ for (SelectorKey key : keys) {
+ String value = key.extractField(scoMessage);
+ if (value == null) {
+ /*
+ * No value for this field, so this message is not relevant to this
+ * forwarder.
+ */
+ return;
+ }
+
+ values.add(value);
+ }
+
+ // get the listeners for this set of values
+ Map<TriConsumer<CommInfrastructure, String, StandardCoderObject>, String> listeners =
+ values2listeners.get(values);
+ if (listeners == null) {
+ // no listeners for this particular list of values
+ return;
+ }
+
+
+ // forward the message to each listener
+ for (TriConsumer<CommInfrastructure, String, StandardCoderObject> listener : listeners.keySet()) {
+ try {
+ listener.accept(infra, textMessage, scoMessage);
+ } catch (RuntimeException e) {
+ logger.warn("exception thrown by listener {}", Util.ident(listener), e);
+ }
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============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);
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicListener;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A topic listener. When a message arrives on a topic, it is forwarded to listeners based
+ * on the content of fields found within the message. However, depending on the message
+ * type, the relevant fields might be found in different places within the message's
+ * object hierarchy. For each different list of keys, this class maintains a
+ * {@link Forwarder}, which is used to forward the message to all relevant listeners.
+ * <p/>
+ * Once a selector has been added, it is not removed until {@link #shutdown()} is invoked.
+ * As selectors are typically only added by Operators, and not by individual Operations,
+ * this should not pose a problem.
+ */
+public class TopicListenerImpl implements TopicListener {
+ private static final Logger logger = LoggerFactory.getLogger(TopicListenerImpl.class);
+ private static StandardCoder coder = new StandardCoder();
+
+ /**
+ * Maps selector to a forwarder.
+ */
+ private final Map<List<SelectorKey>, Forwarder> selector2forwarder = new ConcurrentHashMap<>();
+
+
+ /**
+ * Removes all forwarders.
+ */
+ public void shutdown() {
+ selector2forwarder.clear();
+ }
+
+ /**
+ * Adds a forwarder, if it doesn't already exist.
+ *
+ * @param keys the selector keys
+ * @return the forwarder associated with the given selector keys
+ */
+ public Forwarder addForwarder(SelectorKey... keys) {
+ return addForwarder(Arrays.asList(keys));
+ }
+
+ /**
+ * Adds a forwarder, if it doesn't already exist.
+ *
+ * @param keys the selector keys
+ * @return the forwarder associated with the given selector keys
+ */
+ public Forwarder addForwarder(List<SelectorKey> keys) {
+ return selector2forwarder.computeIfAbsent(keys, key -> new Forwarder(keys));
+ }
+
+ /**
+ * Decodes the message and then forwards it to each forwarder for processing.
+ */
+ @Override
+ public void onTopicEvent(CommInfrastructure infra, String topic, String message) {
+ StandardCoderObject object;
+ try {
+ object = coder.decode(message, StandardCoderObject.class);
+ } catch (CoderException e) {
+ logger.warn("cannot decode message", e);
+ return;
+ }
+
+ /*
+ * We don't know which selector is appropriate for the message, so we just let
+ * them all take a crack at it.
+ */
+ for (Forwarder forwarder : selector2forwarder.values()) {
+ forwarder.onMessage(infra, message, object);
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import lombok.Getter;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A pair of topics, one of which is used to publish requests and the other to receive
+ * responses.
+ */
+public class TopicPair extends TopicListenerImpl {
+ private static final Logger logger = LoggerFactory.getLogger(TopicPair.class);
+
+ @Getter
+ private final String source;
+
+ @Getter
+ private final String target;
+
+ private final List<TopicSink> publishers;
+ private final List<TopicSource> subscribers;
+
+ /**
+ * Constructs the object.
+ *
+ * @param source source topic name
+ * @param target target topic name
+ */
+ public TopicPair(String source, String target) {
+ this.source = source;
+ this.target = target;
+
+ publishers = getTopicEndpointManager().getTopicSinks(target);
+ if (publishers.isEmpty()) {
+ throw new IllegalArgumentException("no sinks for topic: " + target);
+ }
+
+ subscribers = getTopicEndpointManager().getTopicSources(Arrays.asList(source));
+ if (subscribers.isEmpty()) {
+ throw new IllegalArgumentException("no sources for topic: " + source);
+ }
+ }
+
+ /**
+ * Starts listening on the source topic(s).
+ */
+ public void start() {
+ subscribers.forEach(topic -> topic.register(this));
+ }
+
+ /**
+ * Stops listening on the source topic(s).
+ */
+ public void stop() {
+ subscribers.forEach(topic -> topic.unregister(this));
+ }
+
+ /**
+ * Stops listening on the source topic(s) and clears all of the forwarders.
+ */
+ @Override
+ public void shutdown() {
+ stop();
+ super.shutdown();
+ }
+
+ /**
+ * Publishes a message to the target topic.
+ *
+ * @param message message to be published
+ * @return a list of the infrastructures on which it was published
+ */
+ public List<CommInfrastructure> publish(String message) {
+ List<CommInfrastructure> infrastructures = new ArrayList<>(publishers.size());
+
+ for (TopicSink topic : publishers) {
+ try {
+ topic.send(message);
+ infrastructures.add(topic.getTopicCommInfrastructure());
+
+ } catch (RuntimeException e) {
+ logger.warn("cannot publish to {}:{}", topic.getTopicCommInfrastructure(), target, e);
+ }
+ }
+
+ return infrastructures;
+ }
+
+ // these may be overridden by junit tests
+
+ protected TopicEndpoint getTopicEndpointManager() {
+ return TopicEndpointManager.getManager();
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+/**
+ * Manages topic pairs.
+ */
+@FunctionalInterface
+public interface TopicPairManager {
+
+ /**
+ * Gets the topic pair for the given parameters, creating one if it does not exist.
+ *
+ * @param source source topic name
+ * @param target target topic name
+ * @return the topic pair associated with the given source and target topics
+ */
+ TopicPair getTopicPair(String source, String target);
+}
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;
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
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.concurrent.Executor;
+
+/**
+ * Executor that will run tasks until the queue is empty or a maximum number of tasks have
+ * been executed. Doesn't actually run anything until {@link #runAll()} is invoked.
+ */
+public class MyExec implements Executor {
+
+ // TODO move this to policy-common/utils-test
+
+ private final int maxTasks;
+ private final Queue<Runnable> commands = new LinkedList<>();
+
+ public MyExec(int maxTasks) {
+ this.maxTasks = maxTasks;
+ }
+
+ public int getQueueLength() {
+ return commands.size();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ commands.add(command);
+ }
+
+ /**
+ * Runs all tasks until the queue is empty or the maximum number of tasks have been
+ * reached.
+ *
+ * @return {@code true} if the queue is empty, {@code false} if the maximum number of
+ * tasks have been reached before the queue was completed
+ */
+ public boolean runAll() {
+ for (int count = 0; count < maxTasks && !commands.isEmpty(); ++count) {
+ commands.remove().run();
+ }
+
+ return commands.isEmpty();
+ }
+}
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;
event.setRequestId(REQ_ID);
context = new ControlLoopEventContext(event);
- executor = new MyExec();
+ executor = new MyExec(100 * MAX_PARALLEL_REQUESTS);
params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
.executor(executor).actor(ACTOR).operation(OPERATION).timeoutSec(TIMEOUT)
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();
- }
- }
}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import ch.qos.logback.classic.Logger;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.Coder;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.common.utils.test.log.logback.ExtractAppender;
+import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.slf4j.LoggerFactory;
+
+public class TopicPairOperationTest {
+ private static final List<CommInfrastructure> INFRA_LIST =
+ Arrays.asList(CommInfrastructure.NOOP, CommInfrastructure.UEB);
+ private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-operation";
+ private static final String REQ_ID = "my-request-id";
+ private static final String MY_SOURCE = "my-source";
+ private static final String MY_TARGET = "my-target";
+ private static final String TEXT = "some text";
+ private static final int TIMEOUT_SEC = 10;
+ private static final long TIMEOUT_MS = 1000 * TIMEOUT_SEC;
+
+ private static final StandardCoder coder = new StandardCoder();
+
+ /**
+ * Used to attach an appender to the class' logger.
+ */
+ private static final Logger logger = (Logger) LoggerFactory.getLogger(TopicPairOperation.class);
+ private static final ExtractAppender appender = new ExtractAppender();
+
+ @Mock
+ private TopicPairOperator operator;
+ @Mock
+ private TopicPair pair;
+ @Mock
+ private Forwarder forwarder;
+
+ @Captor
+ private ArgumentCaptor<TriConsumer<CommInfrastructure, String, StandardCoderObject>> listenerCaptor;
+
+ private ControlLoopOperationParams params;
+ private TopicPairParams topicParams;
+ private OperationOutcome outcome;
+ private StandardCoderObject stdResponse;
+ private String responseText;
+ private MyExec executor;
+ private TopicPairOperation<MyRequest, MyResponse> oper;
+
+ /**
+ * Attaches the appender to the logger.
+ */
+ @BeforeClass
+ public static void setUpBeforeClass() throws Exception {
+ /**
+ * Attach appender to the logger.
+ */
+ appender.setContext(logger.getLoggerContext());
+ appender.start();
+
+ logger.addAppender(appender);
+ }
+
+ /**
+ * Stops the appender.
+ */
+ @AfterClass
+ public static void tearDownAfterClass() {
+ appender.stop();
+ }
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() throws CoderException {
+ MockitoAnnotations.initMocks(this);
+
+ appender.clearExtractions();
+
+ topicParams = TopicPairParams.builder().source(MY_SOURCE).target(MY_TARGET).timeoutSec(TIMEOUT_SEC).build();
+
+ when(operator.getActorName()).thenReturn(ACTOR);
+ when(operator.getName()).thenReturn(OPERATION);
+ when(operator.getTopicPair()).thenReturn(pair);
+ when(operator.getForwarder()).thenReturn(forwarder);
+ when(operator.getParams()).thenReturn(topicParams);
+ when(operator.isAlive()).thenReturn(true);
+
+ when(pair.publish(any())).thenReturn(INFRA_LIST);
+
+ executor = new MyExec(100);
+
+ params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).executor(executor).build();
+ outcome = params.makeOutcome();
+
+ responseText = coder.encode(new MyResponse());
+ stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+ oper = new MyOperation();
+ }
+
+ @Test
+ public void testTopicPairOperation_testGetTopicPair_testGetForwarder_testGetPairParams() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertSame(pair, oper.getTopicPair());
+ assertSame(forwarder, oper.getForwarder());
+ assertSame(topicParams, oper.getPairParams());
+ assertEquals(TIMEOUT_MS, oper.getTimeoutMs());
+ assertSame(MyResponse.class, oper.getResponseClass());
+ }
+
+ @Test
+ public void testStartOperationAsync() throws Exception {
+ CompletableFuture<OperationOutcome> future = oper.startOperationAsync(1, outcome);
+ assertFalse(future.isDone());
+
+ verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
+
+ verify(forwarder, never()).unregister(any(), any());
+
+ verify(pair).publish(any());
+
+ // provide the response
+ listenerCaptor.getValue().accept(CommInfrastructure.NOOP, responseText, stdResponse);
+
+ // run the tasks
+ assertTrue(executor.runAll());
+
+ assertTrue(future.isDone());
+
+ assertSame(outcome, future.get(5, TimeUnit.SECONDS));
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+
+ verify(forwarder).unregister(eq(Arrays.asList(REQ_ID)), eq(listenerCaptor.getValue()));
+ }
+
+ /**
+ * Tests startOperationAsync() when the publisher throws an exception.
+ */
+ @Test
+ public void testStartOperationAsyncException() throws Exception {
+ // indicate that nothing was published
+ when(pair.publish(any())).thenReturn(Arrays.asList());
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.startOperationAsync(1, outcome));
+
+ verify(forwarder).register(eq(Arrays.asList(REQ_ID)), listenerCaptor.capture());
+
+ // must still unregister
+ verify(forwarder).unregister(eq(Arrays.asList(REQ_ID)), eq(listenerCaptor.getValue()));
+ }
+
+ @Test
+ public void testGetTimeoutMsInteger() {
+ // use default
+ assertEquals(TIMEOUT_MS, oper.getTimeoutMs(null));
+ assertEquals(TIMEOUT_MS, oper.getTimeoutMs(0));
+
+ // use provided value
+ assertEquals(5000, oper.getTimeoutMs(5));
+ }
+
+ @Test
+ public void testPublishRequest() {
+ oper.publishRequest(new MyRequest());
+ assertEquals(INFRA_LIST.size(), appender.getExtracted().size());
+ }
+
+ /**
+ * Tests publishRequest() when nothing is published.
+ */
+ @Test
+ public void testPublishRequestUnpublished() {
+ when(pair.publish(any())).thenReturn(Arrays.asList());
+ assertThatIllegalStateException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+ }
+
+ /**
+ * Tests publishRequest() when the request type is a String.
+ */
+ @Test
+ public void testPublishRequestString() {
+ MyStringOperation oper2 = new MyStringOperation();
+ oper2.publishRequest(TEXT);
+ assertEquals(INFRA_LIST.size(), appender.getExtracted().size());
+ }
+
+ /**
+ * Tests publishRequest() when the coder throws an exception.
+ */
+ @Test
+ public void testPublishRequestException() {
+ setOperCoderException();
+ assertThatIllegalArgumentException().isThrownBy(() -> oper.publishRequest(new MyRequest()));
+ }
+
+ /**
+ * Tests processResponse() when it's a success and the response type is a String.
+ */
+ @Test
+ public void testProcessResponseSuccessString() {
+ MyStringOperation oper2 = new MyStringOperation();
+
+ assertSame(outcome, oper2.processResponse(CommInfrastructure.NOOP, outcome, TEXT, null));
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when it's a success and the response type is a
+ * StandardCoderObject.
+ */
+ @Test
+ public void testProcessResponseSuccessSco() {
+ MyScoOperation oper2 = new MyScoOperation();
+
+ assertSame(outcome, oper2.processResponse(CommInfrastructure.NOOP, outcome, responseText, stdResponse));
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when it's a failure.
+ */
+ @Test
+ public void testProcessResponseFailure() throws CoderException {
+ // indicate error in the response
+ MyResponse resp = new MyResponse();
+ resp.setOutput("error");
+
+ responseText = coder.encode(resp);
+ stdResponse = coder.decode(responseText, StandardCoderObject.class);
+
+ assertSame(outcome, oper.processResponse(CommInfrastructure.NOOP, outcome, responseText, stdResponse));
+ assertEquals(PolicyResult.FAILURE, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when the decoder succeeds.
+ */
+ @Test
+ public void testProcessResponseDecodeOk() throws CoderException {
+ assertSame(outcome, oper.processResponse(CommInfrastructure.NOOP, outcome, responseText, stdResponse));
+ assertEquals(PolicyResult.SUCCESS, outcome.getResult());
+ }
+
+ /**
+ * Tests processResponse() when the decoder throws an exception.
+ */
+ @Test
+ public void testProcessResponseDecodeExcept() throws CoderException {
+ // @formatter:off
+ assertThatIllegalArgumentException().isThrownBy(
+ () -> oper.processResponse(CommInfrastructure.NOOP, outcome, "{invalid json", stdResponse));
+ // @formatter:on
+ }
+
+ @Test
+ public void testPostProcessResponse() {
+ assertThatCode(() -> oper.postProcessResponse(outcome, null, null)).doesNotThrowAnyException();
+ }
+
+ @Test
+ public void testLogTopicRequest() {
+ // nothing to log
+ appender.clearExtractions();
+ oper.logTopicRequest(Arrays.asList(), new MyRequest());
+ assertEquals(0, appender.getExtracted().size());
+
+ // log structured data
+ appender.clearExtractions();
+ oper.logTopicRequest(INFRA_LIST, new MyRequest());
+ List<String> output = appender.getExtracted();
+ assertEquals(2, output.size());
+
+ assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString())
+ .contains("{\n \"theRequestId\": \"my-request-id\"\n}");
+
+ assertThat(output.get(1)).contains(CommInfrastructure.UEB.toString())
+ .contains("{\n \"theRequestId\": \"my-request-id\"\n}");
+
+ // log a plain string
+ appender.clearExtractions();
+ new MyStringOperation().logTopicRequest(Arrays.asList(CommInfrastructure.NOOP), TEXT);
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+ assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains(TEXT);
+
+ // log a null request
+ appender.clearExtractions();
+ oper.logTopicRequest(Arrays.asList(CommInfrastructure.NOOP), null);
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+
+ assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains("null");
+
+ // exception from coder
+ setOperCoderException();
+
+ appender.clearExtractions();
+ oper.logTopicRequest(Arrays.asList(CommInfrastructure.NOOP), new MyRequest());
+ output = appender.getExtracted();
+ assertEquals(2, output.size());
+ assertThat(output.get(0)).contains("cannot pretty-print request");
+ assertThat(output.get(1)).contains(CommInfrastructure.NOOP.toString());
+ }
+
+ @Test
+ public void testLogTopicResponse() {
+ // log structured data
+ appender.clearExtractions();
+ oper.logTopicResponse(CommInfrastructure.NOOP, new MyResponse());
+ List<String> output = appender.getExtracted();
+ assertEquals(1, output.size());
+
+ assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString())
+ .contains("{\n \"requestId\": \"my-request-id\"\n}");
+
+ // log a plain string
+ appender.clearExtractions();
+ new MyStringOperation().logTopicResponse(CommInfrastructure.NOOP, TEXT);
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+ assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains(TEXT);
+
+ // log a null response
+ appender.clearExtractions();
+ oper.logTopicResponse(CommInfrastructure.NOOP, null);
+ output = appender.getExtracted();
+ assertEquals(1, output.size());
+
+ assertThat(output.get(0)).contains(CommInfrastructure.NOOP.toString()).contains("null");
+
+ // exception from coder
+ setOperCoderException();
+
+ appender.clearExtractions();
+ oper.logTopicResponse(CommInfrastructure.NOOP, new MyResponse());
+ output = appender.getExtracted();
+ assertEquals(2, output.size());
+ assertThat(output.get(0)).contains("cannot pretty-print response");
+ assertThat(output.get(1)).contains(CommInfrastructure.NOOP.toString());
+ }
+
+ @Test
+ public void testMakeCoder() {
+ assertNotNull(oper.makeCoder());
+ }
+
+ /**
+ * Creates a new {@link #oper} whose coder will throw an exception.
+ */
+ private void setOperCoderException() {
+ oper = new MyOperation() {
+ @Override
+ protected Coder makeCoder() {
+ return new StandardCoder() {
+ @Override
+ public String encode(Object object, boolean pretty) throws CoderException {
+ throw new CoderException(EXPECTED_EXCEPTION);
+ }
+ };
+ }
+ };
+ }
+
+ @Getter
+ @Setter
+ public static class MyRequest {
+ private String theRequestId = REQ_ID;
+ private String input;
+ }
+
+ @Getter
+ @Setter
+ public static class MyResponse {
+ private String requestId = REQ_ID;
+ private String output;
+ }
+
+
+ private class MyStringOperation extends TopicPairOperation<String, String> {
+ public MyStringOperation() {
+ super(TopicPairOperationTest.this.params, operator, String.class);
+ }
+
+ @Override
+ protected String makeRequest(int attempt) {
+ return TEXT;
+ }
+
+ @Override
+ protected List<String> getExpectedKeyValues(int attempt, String request) {
+ return Arrays.asList(REQ_ID);
+ }
+
+ @Override
+ protected boolean isSuccess(String rawResponse, String response) {
+ return (response != null);
+ }
+ }
+
+
+ private class MyScoOperation extends TopicPairOperation<MyRequest, StandardCoderObject> {
+ public MyScoOperation() {
+ super(TopicPairOperationTest.this.params, operator, StandardCoderObject.class);
+ }
+
+ @Override
+ protected MyRequest makeRequest(int attempt) {
+ return new MyRequest();
+ }
+
+ @Override
+ protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
+ return Arrays.asList(REQ_ID);
+ }
+
+ @Override
+ protected boolean isSuccess(String rawResponse, StandardCoderObject response) {
+ return (response.getString("output") == null);
+ }
+ }
+
+
+ private class MyOperation extends TopicPairOperation<MyRequest, MyResponse> {
+ public MyOperation() {
+ super(TopicPairOperationTest.this.params, operator, MyResponse.class);
+ }
+
+ @Override
+ protected MyRequest makeRequest(int attempt) {
+ return new MyRequest();
+ }
+
+ @Override
+ protected List<String> getExpectedKeyValues(int attempt, MyRequest request) {
+ return Arrays.asList(REQ_ID);
+ }
+
+ @Override
+ protected boolean isSuccess(String rawResponse, MyResponse response) {
+ return (response.getOutput() == null);
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.when;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.controlloop.actorserviceprovider.Operation;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams;
+import org.onap.policy.controlloop.actorserviceprovider.topic.Forwarder;
+import org.onap.policy.controlloop.actorserviceprovider.topic.SelectorKey;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPair;
+import org.onap.policy.controlloop.actorserviceprovider.topic.TopicPairManager;
+
+public class TopicPairOperatorTest {
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-operation";
+ private static final String MY_SOURCE = "my-source";
+ private static final String MY_TARGET = "my-target";
+ private static final int TIMEOUT_SEC = 10;
+
+ @Mock
+ private TopicPairManager mgr;
+ @Mock
+ private TopicPair pair;
+ @Mock
+ private Forwarder forwarder;
+ @Mock
+ private TopicPairOperation<String, Integer> operation;
+
+ private List<SelectorKey> keys;
+ private TopicPairParams params;
+ private MyOperator oper;
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ keys = List.of(new SelectorKey(""));
+
+ when(mgr.getTopicPair(MY_SOURCE, MY_TARGET)).thenReturn(pair);
+ when(pair.addForwarder(keys)).thenReturn(forwarder);
+
+ oper = new MyOperator(keys);
+
+ params = TopicPairParams.builder().source(MY_SOURCE).target(MY_TARGET).timeoutSec(TIMEOUT_SEC).build();
+ oper.configure(Util.translateToMap(OPERATION, params));
+ oper.start();
+ }
+
+ @Test
+ public void testTopicPairOperator_testGetParams_testGetTopicPair_testGetForwarder() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATION, oper.getName());
+ assertEquals(params, oper.getParams());
+ assertSame(pair, oper.getTopicPair());
+ assertSame(forwarder, oper.getForwarder());
+ }
+
+ @Test
+ public void testDoConfigure() {
+ oper.stop();
+
+ // invalid parameters
+ params.setSource(null);
+ assertThatThrownBy(() -> oper.configure(Util.translateToMap(OPERATION, params)))
+ .isInstanceOf(ParameterValidationRuntimeException.class);
+ }
+
+ @Test
+ public void testMakeOperator() {
+ AtomicReference<ControlLoopOperationParams> paramsRef = new AtomicReference<>();
+ AtomicReference<TopicPairOperator> operRef = new AtomicReference<>();
+
+ // @formatter:off
+ BiFunction<ControlLoopOperationParams, TopicPairOperator, TopicPairOperation<String, Integer>> maker =
+ (params, operator) -> {
+ paramsRef.set(params);
+ operRef.set(operator);
+ return operation;
+ };
+ // @formatter:on
+
+ TopicPairOperator oper2 = TopicPairOperator.makeOperator(ACTOR, OPERATION, mgr, maker, new SelectorKey(""));
+
+ assertEquals(ACTOR, oper2.getActorName());
+ assertEquals(OPERATION, oper2.getName());
+
+ ControlLoopOperationParams params2 = ControlLoopOperationParams.builder().build();
+
+ assertSame(operation, oper2.buildOperation(params2));
+ assertSame(params2, paramsRef.get());
+ assertSame(oper2, operRef.get());
+ }
+
+
+ private class MyOperator extends TopicPairOperator {
+ public MyOperator(List<SelectorKey> selectorKeys) {
+ super(ACTOR, OPERATION, mgr, selectorKeys);
+ }
+
+ @Override
+ public Operation buildOperation(ControlLoopOperationParams params) {
+ return null;
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class TopicPairActorParamsTest {
+ private static final String MY_NAME = "my-name";
+
+ private static final String DFLT_SOURCE = "default-source";
+ private static final String DFLT_TARGET = "default-target";
+ private static final int DFLT_TIMEOUT = 10;
+
+ private static final String OPER1_NAME = "oper A";
+ private static final String OPER1_SOURCE = "source A";
+ private static final String OPER1_TARGET = "target A";
+ private static final int OPER1_TIMEOUT = 20;
+
+ // oper2 uses some default values
+ private static final String OPER2_NAME = "oper B";
+ private static final String OPER2_SOURCE = "source B";
+
+ // oper3 uses default values for everything
+ private static final String OPER3_NAME = "oper C";
+
+ private TopicPairParams defaults;
+ private Map<String, Map<String, Object>> operMap;
+ private TopicPairActorParams params;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ defaults = TopicPairParams.builder().source(DFLT_SOURCE).target(DFLT_TARGET).timeoutSec(DFLT_TIMEOUT).build();
+
+ TopicPairParams oper1 = TopicPairParams.builder().source(OPER1_SOURCE).target(OPER1_TARGET)
+ .timeoutSec(OPER1_TIMEOUT).build();
+
+ Map<String, Object> oper1Map = Util.translateToMap(OPER1_NAME, oper1);
+ Map<String, Object> oper2Map = Map.of("source", OPER2_SOURCE);
+ Map<String, Object> oper3Map = Collections.emptyMap();
+ operMap = Map.of(OPER1_NAME, oper1Map, OPER2_NAME, oper2Map, OPER3_NAME, oper3Map);
+
+ params = TopicPairActorParams.builder().defaults(defaults).operation(operMap).build();
+
+ }
+
+ @Test
+ public void testTopicPairActorParams() {
+ assertSame(defaults, params.getDefaults());
+ assertSame(operMap, params.getOperation());
+ }
+
+ @Test
+ public void testDoValidation() {
+ assertSame(params, params.doValidation(MY_NAME));
+
+ // test with invalid parameters
+ defaults.setTimeoutSec(-1);
+ assertThatThrownBy(() -> params.doValidation(MY_NAME)).isInstanceOf(ParameterValidationRuntimeException.class);
+ }
+
+ @Test
+ public void testValidate() {
+ ValidationResult result;
+
+ // null defaults
+ params.setDefaults(null);
+ result = params.validate(MY_NAME);
+ assertFalse(result.isValid());
+ assertThat(result.getResult()).contains("defaults").contains("null");
+ params.setDefaults(defaults);
+
+ // invalid value in defaults
+ defaults.setTimeoutSec(-1);
+ result = params.validate(MY_NAME);
+ assertFalse(result.isValid());
+ assertThat(result.getResult()).contains("defaults").contains("timeoutSec");
+ defaults.setTimeoutSec(DFLT_TIMEOUT);
+
+ // null map
+ params.setOperation(null);
+ result = params.validate(MY_NAME);
+ assertFalse(result.isValid());
+ assertThat(result.getResult()).contains("operation");
+ params.setOperation(operMap);
+
+ // null entry in the map
+ Map<String, Map<String, Object>> map2 = new TreeMap<>(operMap);
+ map2.put(OPER2_NAME, null);
+ params.setOperation(map2);
+ result = params.validate(MY_NAME);
+ assertFalse(result.isValid());
+ assertThat(result.getResult()).contains("operation").contains("null");
+ params.setOperation(operMap);
+
+ // test success case
+ assertTrue(params.validate(MY_NAME).isValid());
+ }
+}
import org.junit.Before;
import org.junit.Test;
import org.onap.policy.common.parameters.ValidationResult;
-import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicParams.TopicParamsBuilder;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicPairParams.TopicPairParamsBuilder;
-public class TopicParamsTest {
+public class TopicPairParamsTest {
private static final String CONTAINER = "my-container";
private static final String TARGET = "my-target";
private static final String SOURCE = "my-source";
- private static final long TIMEOUT = 10;
+ private static final int TIMEOUT = 10;
- private TopicParams params;
+ private TopicPairParams params;
@Before
public void setUp() {
- params = TopicParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
+ params = TopicPairParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
}
@Test
testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1));
// check edge cases
- assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
+ assertFalse(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid());
+
+ // some default values should be valid
+ assertTrue(TopicPairParams.builder().target(TARGET).source(SOURCE).build().validate(CONTAINER).isValid());
}
@Test
}
private void testValidateField(String fieldName, String expected,
- Function<TopicParamsBuilder, TopicParamsBuilder> makeInvalid) {
+ Function<TopicPairParamsBuilder, TopicPairParamsBuilder> makeInvalid) {
// original params should be valid
ValidationResult result = params.validate(CONTAINER);
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+import org.onap.policy.controlloop.actorserviceprovider.Util;
+
+public class ForwarderTest {
+ private static final CommInfrastructure INFRA = CommInfrastructure.NOOP;
+ private static final String TEXT = "some text";
+
+ private static final String KEY1 = "requestId";
+ private static final String KEY2 = "container";
+ private static final String SUBKEY = "subRequestId";
+
+ private static final String VALUEA_REQID = "hello";
+ private static final String VALUEA_SUBREQID = "world";
+
+ // request id is shared with value A
+ private static final String VALUEB_REQID = "hello";
+ private static final String VALUEB_SUBREQID = "another world";
+
+ // unique values
+ private static final String VALUEC_REQID = "bye";
+ private static final String VALUEC_SUBREQID = "bye-bye";
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1;
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1b;
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener2;
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener3;
+
+ private Forwarder forwarder;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ forwarder = new Forwarder(Arrays.asList(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY)));
+
+ forwarder.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+ forwarder.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1b);
+ forwarder.register(Arrays.asList(VALUEB_REQID, VALUEB_SUBREQID), listener2);
+ forwarder.register(Arrays.asList(VALUEC_REQID, VALUEC_SUBREQID), listener3);
+ }
+
+ @Test
+ public void testRegister() {
+ // key size mismatches
+ assertThatIllegalArgumentException().isThrownBy(() -> forwarder.register(Arrays.asList(), listener1))
+ .withMessage("key/value mismatch");
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> forwarder.register(Arrays.asList(VALUEA_REQID), listener1))
+ .withMessage("key/value mismatch");
+ }
+
+ @Test
+ public void testUnregister() {
+ // remove listener1b
+ forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1b);
+
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ verify(listener1).accept(INFRA, TEXT, sco);
+ verify(listener1b, never()).accept(any(), any(), any());
+
+ // remove listener1
+ forwarder.unregister(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener1);
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ // route a message to listener2
+ sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+ verify(listener2).accept(INFRA, TEXT, sco);
+
+ // no more messages to listener1 or 1b
+ verify(listener1).accept(any(), any(), any());
+ verify(listener1b, never()).accept(any(), any(), any());
+ }
+
+ @Test
+ public void testOnMessage() {
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ verify(listener1).accept(INFRA, TEXT, sco);
+ verify(listener1b).accept(INFRA, TEXT, sco);
+
+ // repeat - counts should increment
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ verify(listener1, times(2)).accept(INFRA, TEXT, sco);
+ verify(listener1b, times(2)).accept(INFRA, TEXT, sco);
+
+ // should not have been invoked
+ verify(listener2, never()).accept(any(), any(), any());
+ verify(listener3, never()).accept(any(), any(), any());
+
+ // try other listeners now
+ sco = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+ verify(listener2).accept(INFRA, TEXT, sco);
+
+ sco = makeMessage(Map.of(KEY1, VALUEC_REQID, KEY2, Map.of(SUBKEY, VALUEC_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+ verify(listener3).accept(INFRA, TEXT, sco);
+
+ // message has no listeners
+ sco = makeMessage(Map.of(KEY1, "xyzzy", KEY2, Map.of(SUBKEY, VALUEB_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ // message doesn't have both keys
+ sco = makeMessage(Map.of(KEY1, VALUEA_REQID));
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ // counts should not have incremented
+ verify(listener1, times(2)).accept(any(), any(), any());
+ verify(listener1b, times(2)).accept(any(), any(), any());
+ verify(listener2).accept(any(), any(), any());
+ verify(listener3).accept(any(), any(), any());
+
+ // listener throws an exception
+ doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any(), any());
+ }
+
+ /*
+ * Tests onMessage() when listener1 throws an exception.
+ */
+ @Test
+ public void testOnMessageListenerException1() {
+ doThrow(new IllegalStateException("expected exception")).when(listener1).accept(any(), any(), any());
+
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ verify(listener1b).accept(INFRA, TEXT, sco);
+ }
+
+ /*
+ * Tests onMessage() when listener1b throws an exception.
+ */
+ @Test
+ public void testOnMessageListenerException1b() {
+ doThrow(new IllegalStateException("expected exception")).when(listener1b).accept(any(), any(), any());
+
+ StandardCoderObject sco = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ forwarder.onMessage(INFRA, TEXT, sco);
+
+ verify(listener1).accept(INFRA, TEXT, sco);
+ }
+
+ /**
+ * Makes a message from a map.
+ */
+ private StandardCoderObject makeMessage(Map<String, Object> map) {
+ return Util.translate("", map, StandardCoderObject.class);
+ }
+}
--- /dev/null
+/*-
+ * ============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;
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.util.Arrays;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.utils.PropertyUtils.TriConsumer;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.common.utils.coder.StandardCoderObject;
+
+public class TopicListenerImplTest {
+ private static final StandardCoder coder = new StandardCoder();
+ private static final CommInfrastructure INFRA = CommInfrastructure.NOOP;
+ private static final String MY_TOPIC = "my-topic";
+ private static final String KEY1 = "requestId";
+ private static final String KEY2 = "container";
+ private static final String SUBKEY = "subRequestId";
+
+ private static final String VALUEA_REQID = "hello";
+ private static final String VALUEA_SUBREQID = "world";
+
+ private static final String VALUEB_REQID = "bye";
+
+ private Forwarder forwarder1;
+ private Forwarder forwarder2;
+ private TopicListenerImpl topic;
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1;
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener1b;
+
+ @Mock
+ private TriConsumer<CommInfrastructure, String, StandardCoderObject> listener2;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ topic = new TopicListenerImpl();
+
+ forwarder1 = topic.addForwarder(new SelectorKey(KEY1));
+ forwarder2 = topic.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY));
+
+ assertNotNull(forwarder1);
+ assertNotNull(forwarder2);
+ assertNotSame(forwarder1, forwarder2);
+
+ forwarder1.register(Arrays.asList(VALUEA_REQID), listener1);
+ forwarder1.register(Arrays.asList(VALUEB_REQID), listener1b);
+ forwarder2.register(Arrays.asList(VALUEA_REQID, VALUEA_SUBREQID), listener2);
+ }
+
+ @Test
+ public void testShutdown() {
+ // shut it down, which should clear all forwarders
+ topic.shutdown();
+
+ // should get a new forwarder now
+ Forwarder forwarder = topic.addForwarder(new SelectorKey(KEY1));
+ assertNotSame(forwarder1, forwarder);
+ assertNotSame(forwarder2, forwarder);
+
+ // new forwarder should be unchanged
+ assertSame(forwarder, topic.addForwarder(new SelectorKey(KEY1)));
+ }
+
+ @Test
+ public void testAddForwarder() {
+ assertSame(forwarder1, topic.addForwarder(new SelectorKey(KEY1)));
+ assertSame(forwarder2, topic.addForwarder(new SelectorKey(KEY1), new SelectorKey(KEY2, SUBKEY)));
+ }
+
+ @Test
+ public void testOnTopicEvent() {
+ /*
+ * send a message that should go to listener1 on forwarder1 and listener2 on
+ * forwarder2
+ */
+ String msg = makeMessage(Map.of(KEY1, VALUEA_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ topic.onTopicEvent(INFRA, MY_TOPIC, msg);
+
+ verify(listener1).accept(eq(INFRA), eq(msg), any());
+ verify(listener2).accept(eq(INFRA), eq(msg), any());
+
+ // not to listener1b
+ verify(listener1b, never()).accept(any(), any(), any());
+
+ /*
+ * now send a message that should only go to listener1b on forwarder1
+ */
+ msg = makeMessage(Map.of(KEY1, VALUEB_REQID, KEY2, Map.of(SUBKEY, VALUEA_SUBREQID)));
+ topic.onTopicEvent(INFRA, MY_TOPIC, msg);
+
+ // should route to listener1 on forwarder1 and listener2 on forwarder2
+ verify(listener1b).accept(eq(INFRA), eq(msg), any());
+
+ // try one where the coder throws an exception
+ topic.onTopicEvent(INFRA, MY_TOPIC, "{invalid-json");
+
+ // no extra invocations
+ verify(listener1).accept(any(), any(), any());
+ verify(listener1b).accept(any(), any(), any());
+ verify(listener2).accept(any(), any(), any());
+ }
+
+ /**
+ * Makes a message from a map.
+ */
+ private String makeMessage(Map<String, Object> map) {
+ try {
+ return coder.encode(map);
+ } catch (CoderException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+}
--- /dev/null
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.topic;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
+import org.onap.policy.common.endpoints.event.comm.TopicEndpoint;
+import org.onap.policy.common.endpoints.event.comm.TopicSink;
+import org.onap.policy.common.endpoints.event.comm.TopicSource;
+
+public class TopicPairTest {
+ private static final String UNKNOWN = "unknown";
+ private static final String MY_SOURCE = "pair-source";
+ private static final String MY_TARGET = "pair-target";
+ private static final String TEXT = "some text";
+
+ @Mock
+ private TopicSink publisher1;
+
+ @Mock
+ private TopicSink publisher2;
+
+ @Mock
+ private TopicSource subscriber1;
+
+ @Mock
+ private TopicSource subscriber2;
+
+ @Mock
+ private TopicEndpoint mgr;
+
+ private TopicPair pair;
+
+
+ /**
+ * Sets up.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mgr.getTopicSinks(MY_TARGET)).thenReturn(Arrays.asList(publisher1, publisher2));
+ when(mgr.getTopicSources(eq(Arrays.asList(MY_SOURCE)))).thenReturn(Arrays.asList(subscriber1, subscriber2));
+
+ when(publisher1.getTopicCommInfrastructure()).thenReturn(CommInfrastructure.NOOP);
+ when(publisher2.getTopicCommInfrastructure()).thenReturn(CommInfrastructure.UEB);
+
+ pair = new MyTopicPair(MY_SOURCE, MY_TARGET);
+
+ pair.start();
+ }
+
+ @Test
+ public void testTopicPair_testGetSource_testGetTarget() {
+ assertEquals(MY_SOURCE, pair.getSource());
+ assertEquals(MY_TARGET, pair.getTarget());
+
+ verify(mgr).getTopicSinks(anyString());
+ verify(mgr).getTopicSources(any());
+
+ // source not found
+ assertThatIllegalArgumentException().isThrownBy(() -> new MyTopicPair(UNKNOWN, MY_TARGET))
+ .withMessageContaining("sources").withMessageContaining(UNKNOWN);
+
+ // target not found
+ assertThatIllegalArgumentException().isThrownBy(() -> new MyTopicPair(MY_SOURCE, UNKNOWN))
+ .withMessageContaining("sinks").withMessageContaining(UNKNOWN);
+ }
+
+ @Test
+ public void testShutdown() {
+ pair.shutdown();
+ verify(subscriber1).unregister(pair);
+ verify(subscriber2).unregister(pair);
+ }
+
+ @Test
+ public void testStart() {
+ verify(subscriber1).register(pair);
+ verify(subscriber2).register(pair);
+ }
+
+ @Test
+ public void testStop() {
+ pair.stop();
+ verify(subscriber1).unregister(pair);
+ verify(subscriber2).unregister(pair);
+ }
+
+ @Test
+ public void testPublish() {
+ List<CommInfrastructure> infrastructures = pair.publish(TEXT);
+ assertEquals(Arrays.asList(CommInfrastructure.NOOP, CommInfrastructure.UEB), infrastructures);
+
+ verify(publisher1).send(TEXT);
+ verify(publisher2).send(TEXT);
+
+ // first one throws an exception - should have only published to the second
+ when(publisher1.send(any())).thenThrow(new IllegalStateException("expected exception"));
+
+ infrastructures = pair.publish(TEXT);
+ assertEquals(Arrays.asList(CommInfrastructure.UEB), infrastructures);
+
+ verify(publisher2, times(2)).send(TEXT);
+ }
+
+ @Test
+ public void testGetTopicEndpointManager() {
+ // setting "mgr" to null should cause it to use the superclass' method
+ mgr = null;
+ assertNotNull(pair.getTopicEndpointManager());
+ }
+
+
+ private class MyTopicPair extends TopicPair {
+ public MyTopicPair(String source, String target) {
+ super(source, target);
+ }
+
+ @Override
+ protected TopicEndpoint getTopicEndpointManager() {
+ return (mgr != null ? mgr : super.getTopicEndpointManager());
+ }
+ }
+}
<logger name="org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperation" level="info" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
+
+ <!-- this is required for TopicPairOperationTest -->
+ <logger
+ name="org.onap.policy.controlloop.actorserviceprovider.impl.TopicPairOperation"
+ level="info" additivity="false">
+ <appender-ref ref="STDOUT" />
+ </logger>
</configuration>
<modules>
<module>actorServiceProvider</module>
<module>actor.test</module>
+ <module>actor.aai</module>
<module>actor.appc</module>
<module>actor.vfc</module>
<module>actor.sdnc</module>
/*-
* ============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.
// @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
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 =
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;
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.
* @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;
createPolicyTypes();
}
- /**
- * Set up standard coder.
- */
- @Before
- public void setupStandardCoder() {
- standardCoder = new StandardCoder();
- }
-
@After
public void teardown() throws Exception {
databaseProvider.close();
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);
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);
*/
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;
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;
return updatedLegacyGuardPolicyMap;
}
-
/**
* Delete legacy guard policy.
*
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"));
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);
}
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);
"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();
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;
}
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,
"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;
}
/**
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);
}
}
// 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);
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()
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;
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);
}
}
}
if (!result.isValid()) {
- throw new PfModelRuntimeException(Response.Status.BAD_REQUEST, result.toString());
+ throw new PfModelRuntimeException(Response.Status.NOT_ACCEPTABLE, result.toString());
}
entityTypes.getConceptMap().entrySet()
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);
}
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");
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);
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);
filter = ToscaPolicyFilter.builder().name("operational.modifyconfig").version(VERSION_100).build();
filteredList = filter.filter(policyList);
- assertEquals(1, filteredList.size());
+ assertEquals(0, filteredList.size());
}
@Test
// 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);
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);
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();
filter = ToscaPolicyFilter.builder().type("onap.policies.controlloop.Operational").typeVersion(VERSION_000)
.build();
filteredList = filter.filter(policyList);
- assertEquals(1, filteredList.size());
+ assertEquals(0, filteredList.size());
}
}
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);
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();
*/
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;
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());
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()));
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
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());
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()));
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()));
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());
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());
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());
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
/*-
* ============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.
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);
/*-
* ============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.
*/
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;
/*-
* ============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.
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();
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();
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());
}
}
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