diff options
Diffstat (limited to 'models-interactions/model-actors')
63 files changed, 9145 insertions, 88 deletions
diff --git a/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java b/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java index e2c997bca..0da1e2a27 100644 --- a/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java +++ b/models-interactions/model-actors/actor.appc/src/main/java/org/onap/policy/controlloop/actor/appc/AppcActorServiceProvider.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * APPCActorServiceProvider * ================================================================================ - * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,13 +33,15 @@ import org.onap.policy.common.utils.coder.CoderException; import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; import org.onap.policy.controlloop.policy.Policy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class AppcActorServiceProvider implements Actor { +public class AppcActorServiceProvider extends ActorImpl { + private static final String NAME = "APPC"; + private static final Logger logger = LoggerFactory.getLogger(AppcActorServiceProvider.class); private static final StandardCoder coder = new StandardCoder(); @@ -62,9 +64,13 @@ public class AppcActorServiceProvider implements Actor { private static final ImmutableMap<String, List<String>> payloads = new ImmutableMap.Builder<String, List<String>>() .put(RECIPE_MODIFY, ImmutableList.of("generic-vnf.vnf-id")).build(); + public AppcActorServiceProvider() { + super(NAME); + } + @Override public String actor() { - return "APPC"; + return NAME; } @Override diff --git a/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmActorServiceProvider.java b/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmActorServiceProvider.java index fb307587d..47898f7d8 100644 --- a/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmActorServiceProvider.java +++ b/models-interactions/model-actors/actor.appclcm/src/main/java/org/onap/policy/controlloop/actor/appclcm/AppcLcmActorServiceProvider.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * AppcLcmActorServiceProvider * ================================================================================ - * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved. * Modifications copyright (c) 2018 Nokia * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ @@ -38,13 +38,15 @@ import org.onap.policy.appclcm.AppcLcmOutput; import org.onap.policy.appclcm.AppcLcmResponseCode; import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; import org.onap.policy.controlloop.policy.Policy; import org.onap.policy.controlloop.policy.PolicyResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class AppcLcmActorServiceProvider implements Actor { +public class AppcLcmActorServiceProvider extends ActorImpl { + + private static final String NAME = "APPC"; private static final Logger logger = LoggerFactory.getLogger(AppcLcmActorServiceProvider.class); @@ -74,9 +76,13 @@ public class AppcLcmActorServiceProvider implements Actor { new ImmutableMap.Builder<String, List<String>>().put(RECIPE_RESTART, ImmutableList.of(APPC_VM_ID)) .put(RECIPE_MODIFY, ImmutableList.of(APPC_REQUEST_PARAMS, APPC_CONFIG_PARAMS)).build(); + public AppcLcmActorServiceProvider() { + super(NAME); + } + @Override public String actor() { - return "APPC"; + return NAME; } @Override diff --git a/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java index df13ba306..05ff02e5c 100644 --- a/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java +++ b/models-interactions/model-actors/actor.cds/src/main/java/org/onap/policy/controlloop/actor/cds/CdsActorServiceProvider.java @@ -1,6 +1,7 @@ /*- * ============LICENSE_START======================================================= * Copyright (C) 2019 Bell Canada. All rights reserved. + * Modifications 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. @@ -47,7 +48,7 @@ import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; import org.onap.policy.controlloop.actor.cds.constants.CdsActorConstants; import org.onap.policy.controlloop.actor.cds.request.CdsActionRequest; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; import org.onap.policy.controlloop.policy.Policy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,10 +56,14 @@ import org.slf4j.LoggerFactory; /** * CDS Actor service-provider implementation. This is a deploy dark feature for El-Alto release. */ -public class CdsActorServiceProvider implements Actor { +public class CdsActorServiceProvider extends ActorImpl { private static final Logger LOGGER = LoggerFactory.getLogger(CdsActorServiceProvider.class); + public CdsActorServiceProvider() { + super(CdsActorConstants.CDS_ACTOR); + } + /** * {@inheritDoc}. */ diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java new file mode 100644 index 000000000..2927bd85b --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperator.java @@ -0,0 +1,108 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import java.util.UUID; +import org.apache.commons.lang3.StringUtils; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.sdnc.SdncHealRequest; +import org.onap.policy.sdnc.SdncHealRequestHeaderInfo; +import org.onap.policy.sdnc.SdncHealRequestInfo; +import org.onap.policy.sdnc.SdncHealServiceInfo; +import org.onap.policy.sdnc.SdncHealVfModuleInfo; +import org.onap.policy.sdnc.SdncHealVfModuleParameter; +import org.onap.policy.sdnc.SdncHealVfModuleParametersInfo; +import org.onap.policy.sdnc.SdncHealVfModuleRequestInput; +import org.onap.policy.sdnc.SdncHealVnfInfo; +import org.onap.policy.sdnc.SdncRequest; + +public class BandwidthOnDemandOperator extends SdncOperator { + public static final String NAME = "BandwidthOnDemand"; + + public static final String URI = "/GENERIC-RESOURCE-API:vf-module-topology-operation"; + + // fields in the enrichment data + public static final String SERVICE_ID_KEY = "service-instance.service-instance-id"; + public static final String VNF_ID = "vnfId"; + + /** + * Constructs the object. + * + * @param actorName name of the actor with which this operator is associated + */ + public BandwidthOnDemandOperator(String actorName) { + super(actorName, NAME); + } + + @Override + protected SdncRequest constructRequest(ControlLoopEventContext context) { + String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY); + if (StringUtils.isBlank(serviceInstance)) { + throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY); + } + + SdncHealVfModuleParameter bandwidth = new SdncHealVfModuleParameter(); + bandwidth.setName("bandwidth"); + bandwidth.setValue(context.getEnrichment().get("bandwidth")); + + SdncHealVfModuleParameter timeStamp = new SdncHealVfModuleParameter(); + timeStamp.setName("bandwidth-change-time"); + timeStamp.setValue(context.getEnrichment().get("bandwidth-change-time")); + + SdncHealVfModuleParametersInfo vfParametersInfo = new SdncHealVfModuleParametersInfo(); + vfParametersInfo.addParameters(bandwidth); + vfParametersInfo.addParameters(timeStamp); + + SdncHealVfModuleRequestInput vfRequestInfo = new SdncHealVfModuleRequestInput(); + vfRequestInfo.setVfModuleParametersInfo(vfParametersInfo); + + SdncHealServiceInfo serviceInfo = new SdncHealServiceInfo(); + serviceInfo.setServiceInstanceId(serviceInstance); + + SdncHealRequestInfo requestInfo = new SdncHealRequestInfo(); + requestInfo.setRequestAction("SdwanBandwidthChange"); + + SdncHealRequestHeaderInfo headerInfo = new SdncHealRequestHeaderInfo(); + headerInfo.setSvcAction("update"); + headerInfo.setSvcRequestId(UUID.randomUUID().toString()); + + SdncRequest request = new SdncRequest(); + request.setNsInstanceId(serviceInstance); + request.setRequestId(context.getRequestId()); + request.setUrl(URI); + + SdncHealVnfInfo vnfInfo = new SdncHealVnfInfo(); + vnfInfo.setVnfId(context.getEnrichment().get(VNF_ID)); + + SdncHealVfModuleInfo vfModuleInfo = new SdncHealVfModuleInfo(); + vfModuleInfo.setVfModuleId(""); + + SdncHealRequest healRequest = new SdncHealRequest(); + healRequest.setVnfInfo(vnfInfo); + healRequest.setVfModuleInfo(vfModuleInfo); + healRequest.setRequestHeaderInfo(headerInfo); + healRequest.setVfModuleRequestInput(vfRequestInfo); + healRequest.setRequestInfo(requestInfo); + healRequest.setServiceInfo(serviceInfo); + request.setHealRequest(healRequest); + return request; + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java new file mode 100644 index 000000000..da400f8eb --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperator.java @@ -0,0 +1,87 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import java.util.UUID; +import org.apache.commons.lang3.StringUtils; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.sdnc.SdncHealNetworkInfo; +import org.onap.policy.sdnc.SdncHealRequest; +import org.onap.policy.sdnc.SdncHealRequestHeaderInfo; +import org.onap.policy.sdnc.SdncHealRequestInfo; +import org.onap.policy.sdnc.SdncHealServiceInfo; +import org.onap.policy.sdnc.SdncRequest; + +public class RerouteOperator extends SdncOperator { + public static final String NAME = "Reroute"; + + public static final String URI = "/GENERIC-RESOURCE-API:network-topology-operation"; + + // fields in the enrichment data + public static final String SERVICE_ID_KEY = "service-instance.service-instance-id"; + public static final String NETWORK_ID_KEY = "network-information.network-id"; + + /** + * Constructs the object. + * + * @param actorName name of the actor with which this operator is associated + */ + public RerouteOperator(String actorName) { + super(actorName, NAME); + } + + @Override + protected SdncRequest constructRequest(ControlLoopEventContext context) { + String serviceInstance = context.getEnrichment().get(SERVICE_ID_KEY); + if (StringUtils.isBlank(serviceInstance)) { + throw new IllegalArgumentException("missing enrichment data, " + SERVICE_ID_KEY); + } + SdncHealServiceInfo serviceInfo = new SdncHealServiceInfo(); + serviceInfo.setServiceInstanceId(serviceInstance); + + String networkId = context.getEnrichment().get(NETWORK_ID_KEY); + if (StringUtils.isBlank(networkId)) { + throw new IllegalArgumentException("missing enrichment data, " + NETWORK_ID_KEY); + } + SdncHealNetworkInfo networkInfo = new SdncHealNetworkInfo(); + networkInfo.setNetworkId(networkId); + + SdncHealRequestInfo requestInfo = new SdncHealRequestInfo(); + requestInfo.setRequestAction("ReoptimizeSOTNInstance"); + + SdncHealRequestHeaderInfo headerInfo = new SdncHealRequestHeaderInfo(); + headerInfo.setSvcAction("reoptimize"); + headerInfo.setSvcRequestId(UUID.randomUUID().toString()); + + SdncRequest request = new SdncRequest(); + request.setNsInstanceId(serviceInstance); + request.setRequestId(context.getRequestId()); + request.setUrl(URI); + + SdncHealRequest healRequest = new SdncHealRequest(); + healRequest.setRequestHeaderInfo(headerInfo); + healRequest.setNetworkInfo(networkInfo); + healRequest.setRequestInfo(requestInfo); + healRequest.setServiceInfo(serviceInfo); + request.setHealRequest(healRequest); + return request; + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java index 24d019f41..8dc8ba50d 100644 --- a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java +++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProvider.java @@ -4,7 +4,7 @@ * ================================================================================ * Copyright (C) 2018-2019 Huawei Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. - * Modifications Copyright (C) 2019 AT&T Intellectual Property. + * Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import java.util.List; import java.util.UUID; import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.HttpActor; import org.onap.policy.controlloop.policy.Policy; import org.onap.policy.sdnc.SdncHealNetworkInfo; import org.onap.policy.sdnc.SdncHealRequest; @@ -45,10 +45,13 @@ import org.onap.policy.sdnc.SdncRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -public class SdncActorServiceProvider implements Actor { +public class SdncActorServiceProvider extends HttpActor { private static final Logger logger = LoggerFactory.getLogger(SdncActorServiceProvider.class); + public static final String NAME = "SDNC"; + + // TODO old code: remove lines down to **HERE** + // Strings for Sdnc Actor private static final String SDNC_ACTOR = "SDNC"; @@ -62,8 +65,23 @@ public class SdncActorServiceProvider implements Actor { private static final String RECIPE_BW_ON_DEMAND = "BandwidthOnDemand"; private static final ImmutableList<String> recipes = ImmutableList.of(RECIPE_REROUTE); - private static final ImmutableMap<String, List<String>> targets = - new ImmutableMap.Builder<String, List<String>>().put(RECIPE_REROUTE, ImmutableList.of(TARGET_VM)).build(); + private static final ImmutableMap<String, List<String>> targets = new ImmutableMap.Builder<String, List<String>>() + .put(RECIPE_REROUTE, ImmutableList.of(TARGET_VM)).build(); + + // **HERE** + + /** + * Constructs the object. + */ + public SdncActorServiceProvider() { + super(NAME); + + addOperator(new RerouteOperator(NAME)); + addOperator(new BandwidthOnDemandOperator(NAME)); + } + + + // TODO old code: remove lines down to **HERE** @Override public String actor() { @@ -93,16 +111,15 @@ public class SdncActorServiceProvider implements Actor { * @param policy the policy * @return the constructed request */ - public SdncRequest constructRequest(VirtualControlLoopEvent onset, ControlLoopOperation operation, - Policy policy) { + public SdncRequest constructRequest(VirtualControlLoopEvent onset, ControlLoopOperation operation, Policy policy) { switch (policy.getRecipe()) { case RECIPE_REROUTE: return constructReOptimizeRequest(onset); case RECIPE_BW_ON_DEMAND: - logger.info("Construct request for receipe {}" , RECIPE_BW_ON_DEMAND); + logger.info("Construct request for receipe {}", RECIPE_BW_ON_DEMAND); return constructBwOnDemandRequest(onset); default: - logger.info("Unsupported recipe {} " + policy.getRecipe()); + logger.info("Unsupported recipe {}", policy.getRecipe()); return null; } } @@ -199,4 +216,6 @@ public class SdncActorServiceProvider implements Actor { request.setHealRequest(healRequest); return request; } + + // **HERE** } diff --git a/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java new file mode 100644 index 000000000..479ee908d --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/main/java/org/onap/policy/controlloop/actor/sdnc/SdncOperator.java @@ -0,0 +1,148 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import javax.ws.rs.client.Entity; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.onap.policy.common.endpoints.http.client.HttpClient; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.controlloop.actorserviceprovider.AsyncResponseHandler; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.impl.HttpOperator; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.sdnc.SdncRequest; +import org.onap.policy.sdnc.SdncResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Superclass for SDNC Operators. + */ +public abstract class SdncOperator extends HttpOperator { + private static final Logger logger = LoggerFactory.getLogger(SdncOperator.class); + + /** + * Constructs the object. + * + * @param actorName name of the actor with which this operator is associated + * @param name operation name + */ + public SdncOperator(String actorName, String name) { + super(actorName, name); + } + + @Override + protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt, + OperationOutcome outcome) { + + SdncRequest request = constructRequest(params.getContext()); + return postRequest(params, outcome, request); + } + + /** + * Constructs the request. + * + * @param context associated event context + * @return a new request + */ + protected abstract SdncRequest constructRequest(ControlLoopEventContext context); + + /** + * Posts the request and and arranges to retrieve the response. + * + * @param params operation parameters + * @param outcome updated with the response + * @param sdncRequest request to be posted + * @return the result of the request + */ + private CompletableFuture<OperationOutcome> postRequest(ControlLoopOperationParams params, OperationOutcome outcome, + SdncRequest sdncRequest) { + Map<String, Object> headers = new HashMap<>(); + + headers.put("Accept", "application/json"); + String sdncUrl = getClient().getBaseUrl(); + + Util.logRestRequest(sdncUrl, sdncRequest); + + Entity<SdncRequest> entity = Entity.entity(sdncRequest, MediaType.APPLICATION_JSON); + + ResponseHandler handler = new ResponseHandler(params, outcome, sdncUrl); + return handler.handle(getClient().post(handler, getPath(), entity, headers)); + } + + private class ResponseHandler extends AsyncResponseHandler<Response> { + private final String sdncUrl; + + public ResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome, String sdncUrl) { + super(params, outcome); + this.sdncUrl = sdncUrl; + } + + /** + * Handles the response. + */ + @Override + protected OperationOutcome doComplete(Response rawResponse) { + String strResponse = HttpClient.getBody(rawResponse, String.class); + + Util.logRestResponse(sdncUrl, strResponse); + + SdncResponse response; + try { + response = makeDecoder().decode(strResponse, SdncResponse.class); + } catch (CoderException e) { + logger.warn("Sdnc Heal cannot decode response with http error code {}", rawResponse.getStatus(), e); + return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION); + } + + if (response.getResponseOutput() != null && "200".equals(response.getResponseOutput().getResponseCode())) { + return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.SUCCESS); + + } else { + logger.info("Sdnc Heal Restcall failed with http error code {}", rawResponse.getStatus()); + return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE); + } + } + + /** + * Handles exceptions. + */ + @Override + protected OperationOutcome doFailed(Throwable thrown) { + logger.info("Sdnc Heal Restcall threw an exception", thrown); + return SdncOperator.this.setOutcome(getParams(), getOutcome(), PolicyResult.FAILURE_EXCEPTION); + } + } + + // these may be overridden by junit tests + + protected StandardCoder makeDecoder() { + return new StandardCoder(); + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java new file mode 100644 index 000000000..02931a4f1 --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BandwidthOnDemandOperatorTest.java @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.sdnc.SdncRequest; + +public class BandwidthOnDemandOperatorTest extends BasicOperator { + + private BandwidthOnDemandOperator oper; + + + /** + * Set up. + */ + @Before + public void setUp() { + makeContext(); + oper = new BandwidthOnDemandOperator(ACTOR); + } + + @Test + public void testBandwidthOnDemandOperator() { + assertEquals(ACTOR, oper.getActorName()); + assertEquals(BandwidthOnDemandOperator.NAME, oper.getName()); + } + + @Test + public void testConstructRequest() throws CoderException { + SdncRequest request = oper.constructRequest(context); + assertEquals("my-service", request.getNsInstanceId()); + assertEquals(REQ_ID, request.getRequestId()); + assertEquals(BandwidthOnDemandOperator.URI, request.getUrl()); + assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId()); + + verifyRequest("bod.json", request); + + verifyMissing(oper, BandwidthOnDemandOperator.SERVICE_ID_KEY, "service"); + } + + @Override + protected Map<String, String> makeEnrichment() { + return Map.of(BandwidthOnDemandOperator.SERVICE_ID_KEY, "my-service", BandwidthOnDemandOperator.VNF_ID, + "my-vnf"); + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java new file mode 100644 index 000000000..b9028d462 --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/BasicOperator.java @@ -0,0 +1,94 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.Assert.assertEquals; + +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; + +/** + * Superclass for various operator tests. + */ +public abstract class BasicOperator { + protected static final UUID REQ_ID = UUID.randomUUID(); + protected static final String ACTOR = "my-actor"; + + protected Map<String, String> enrichment; + protected VirtualControlLoopEvent event; + protected ControlLoopEventContext context; + + /** + * Pretty-prints a request and verifies that the result matches the expected JSON. + * + * @param <T> request type + * @param expectedJsonFile name of the file containing the expected JSON + * @param request request to verify + * @throws CoderException if the request cannot be pretty-printed + */ + protected <T> void verifyRequest(String expectedJsonFile, T request) throws CoderException { + String json = new StandardCoder().encode(request, true); + String expected = ResourceUtils.getResourceAsString(expectedJsonFile); + + // strip request id, because it changes each time + final String stripper = "svc-request-id[^,]*"; + json = json.replaceFirst(stripper, "").trim(); + expected = expected.replaceFirst(stripper, "").trim(); + + assertEquals(expected, json); + } + + /** + * Verifies that an exception is thrown if a field is missing from the enrichment + * data. + * + * @param oper operator to construct the request + * @param fieldName name of the field to be removed from the enrichment data + * @param expectedText text expected in the exception message + */ + protected void verifyMissing(SdncOperator oper, String fieldName, String expectedText) { + makeContext(); + enrichment.remove(fieldName); + + assertThatIllegalArgumentException().isThrownBy(() -> oper.constructRequest(context)) + .withMessageContaining("missing").withMessageContaining(expectedText); + } + + protected void makeContext() { + // need a mutable map, so make a copy + enrichment = new TreeMap<>(makeEnrichment()); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + event.setAai(enrichment); + + context = new ControlLoopEventContext(event); + } + + protected abstract Map<String, String> makeEnrichment(); +} diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java new file mode 100644 index 000000000..0a7bcad6f --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/RerouteOperatorTest.java @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.sdnc.SdncRequest; + +public class RerouteOperatorTest extends BasicOperator { + + private RerouteOperator oper; + + + /** + * Set up. + */ + @Before + public void setUp() { + makeContext(); + oper = new RerouteOperator(ACTOR); + } + + @Test + public void testRerouteOperator() { + assertEquals(ACTOR, oper.getActorName()); + assertEquals(RerouteOperator.NAME, oper.getName()); + } + + @Test + public void testConstructRequest() throws CoderException { + SdncRequest request = oper.constructRequest(context); + assertEquals("my-service", request.getNsInstanceId()); + assertEquals(REQ_ID, request.getRequestId()); + assertEquals(RerouteOperator.URI, request.getUrl()); + assertNotNull(request.getHealRequest().getRequestHeaderInfo().getSvcRequestId()); + + verifyRequest("reroute.json", request); + + verifyMissing(oper, RerouteOperator.SERVICE_ID_KEY, "service"); + verifyMissing(oper, RerouteOperator.NETWORK_ID_KEY, "network"); + } + + @Override + protected Map<String, String> makeEnrichment() { + return Map.of(RerouteOperator.SERVICE_ID_KEY, "my-service", RerouteOperator.NETWORK_ID_KEY, "my-network"); + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java index 9739c7145..08655c349 100644 --- a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java +++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncActorServiceProviderTest.java @@ -3,7 +3,7 @@ * TestSdncActorServiceProvider * ================================================================================ * Copyright (C) 2018-2019 Huawei. All rights reserved. - * Modifications Copyright (C) 2018-2019 AT&T Corp. All rights reserved. + * Modifications Copyright (C) 2018-2020 AT&T Corp. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +26,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import java.util.Arrays; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -36,19 +38,19 @@ import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; import org.onap.policy.controlloop.policy.Policy; import org.onap.policy.sdnc.SdncRequest; -import org.onap.policy.simulators.Util; public class SdncActorServiceProviderTest { - private static final String REROUTE = "Reroute"; + private static final String REROUTE = RerouteOperator.NAME; /** * Set up before test class. + * * @throws Exception if the A&AI simulator cannot be started */ @BeforeClass public static void setUpSimulator() throws Exception { - Util.buildAaiSim(); + org.onap.policy.simulators.Util.buildAaiSim(); } @AfterClass @@ -57,6 +59,18 @@ public class SdncActorServiceProviderTest { } @Test + public void testSdncActorServiceProvider() { + final SdncActorServiceProvider prov = new SdncActorServiceProvider(); + + // verify that it has the operators we expect + var expected = Arrays.asList(BandwidthOnDemandOperator.NAME, RerouteOperator.NAME).stream().sorted() + .collect(Collectors.toList()); + var actual = prov.getOperationNames().stream().sorted().collect(Collectors.toList()); + + assertEquals(expected.toString(), actual.toString()); + } + + @Test public void testConstructRequest() { VirtualControlLoopEvent onset = new VirtualControlLoopEvent(); ControlLoopOperation operation = new ControlLoopOperation(); @@ -84,8 +98,7 @@ public class SdncActorServiceProviderTest { policy.setRecipe(REROUTE); assertNotNull(provider.constructRequest(onset, operation, policy)); - SdncRequest request = - provider.constructRequest(onset, operation, policy); + SdncRequest request = provider.constructRequest(onset, operation, policy); assertEquals(requestId, Objects.requireNonNull(request).getRequestId()); assertEquals("reoptimize", request.getHealRequest().getRequestHeaderInfo().getSvcAction()); diff --git a/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java new file mode 100644 index 000000000..25d383eb8 --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/test/java/org/onap/policy/controlloop/actor/sdnc/SdncOperatorTest.java @@ -0,0 +1,326 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actor.sdnc; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import lombok.Setter; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams; +import org.onap.policy.common.endpoints.event.comm.bus.internal.BusTopicParams.TopicParamsBuilder; +import org.onap.policy.common.endpoints.http.client.HttpClient; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.common.endpoints.http.server.HttpServletServer; +import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance; +import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties; +import org.onap.policy.common.gson.GsonMessageBodyHandler; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.network.NetworkUtil; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.sdnc.SdncHealRequest; +import org.onap.policy.sdnc.SdncRequest; +import org.onap.policy.sdnc.SdncResponse; +import org.onap.policy.sdnc.SdncResponseOutput; + +public class SdncOperatorTest { + public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json"; + private static final String EXPECTED_EXCEPTION = "expected exception"; + public static final String HTTP_CLIENT = "my-http-client"; + public static final String HTTP_NO_SERVER = "my-http-no-server-client"; + private static final String ACTOR = "my-actor"; + private static final String OPERATION = "my-operation"; + + /** + * Outcome to be added to the response. + */ + @Setter + private static SdncResponseOutput output; + + + private VirtualControlLoopEvent event; + private ControlLoopEventContext context; + private MyOper oper; + + /** + * Starts the SDNC simulator. + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // allocate a port + int port = NetworkUtil.allocPort(); + + /* + * Start the simulator. Must use "Properties" to configure it, otherwise the + * server will use the wrong serialization provider. + */ + Properties svrprops = getServerProperties("my-server", port); + HttpServletServerFactoryInstance.getServerFactory().build(svrprops).forEach(HttpServletServer::start); + + /* + * Start the clients, one to the server, and one to a non-existent server. + */ + TopicParamsBuilder builder = BusTopicParams.builder().managed(true).hostname("localhost").basePath("sdnc") + .serializationProvider(GsonMessageBodyHandler.class.getName()); + + HttpClientFactoryInstance.getClientFactory().build(builder.clientName(HTTP_CLIENT).port(port).build()); + + HttpClientFactoryInstance.getClientFactory() + .build(builder.clientName(HTTP_NO_SERVER).port(NetworkUtil.allocPort()).build()); + } + + @AfterClass + public static void tearDownAfterClass() { + HttpClientFactoryInstance.getClientFactory().destroy(); + HttpServletServerFactoryInstance.getServerFactory().destroy(); + } + + /** + * Initializes {@link #oper} and sets {@link #output} to a success code. + */ + @Before + public void setUp() { + event = new VirtualControlLoopEvent(); + context = new ControlLoopEventContext(event); + + initOper(HTTP_CLIENT); + + output = new SdncResponseOutput(); + output.setResponseCode("200"); + } + + @After + public void tearDown() { + oper.shutdown(); + } + + @Test + public void testSdncOperator() { + assertEquals(ACTOR, oper.getActorName()); + assertEquals(OPERATION, oper.getName()); + assertEquals(ACTOR + "." + OPERATION, oper.getFullName()); + } + + @Test + public void testGetClient() { + assertNotNull(oper.getTheClient()); + } + + @Test + public void testStartOperationAsync_testPostRequest() throws Exception { + OperationOutcome outcome = runOperation(); + assertNotNull(outcome); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + } + + /** + * Tests postRequest() when decode() throws an exception. + */ + @Test + public void testPostRequestDecodeException() throws Exception { + + oper.setDecodeFailure(true); + + OperationOutcome outcome = runOperation(); + assertNotNull(outcome); + assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult()); + } + + /** + * Tests postRequest() when there is no "output" field in the response. + */ + @Test + public void testPostRequestNoOutput() throws Exception { + + setOutput(null); + + OperationOutcome outcome = runOperation(); + assertNotNull(outcome); + assertEquals(PolicyResult.FAILURE, outcome.getResult()); + } + + /** + * Tests postRequest() when the output is not a success. + */ + @Test + public void testPostRequestOutputFailure() throws Exception { + + output.setResponseCode(null); + + OperationOutcome outcome = runOperation(); + assertNotNull(outcome); + assertEquals(PolicyResult.FAILURE, outcome.getResult()); + } + + /** + * Tests postRequest() when the post() request throws an exception retrieving the + * response. + */ + @Test + public void testPostRequestException() throws Exception { + + // reset "oper" to point to a non-existent server + oper.shutdown(); + initOper(HTTP_NO_SERVER); + + OperationOutcome outcome = runOperation(); + assertNotNull(outcome); + assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult()); + } + + private static Properties getServerProperties(String name, int port) { + final Properties props = new Properties(); + props.setProperty(PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES, name); + + final String svcpfx = PolicyEndPointProperties.PROPERTY_HTTP_SERVER_SERVICES + "." + name; + + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_REST_CLASSES_SUFFIX, Server.class.getName()); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_HOST_SUFFIX, "localhost"); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_PORT_SUFFIX, String.valueOf(port)); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_MANAGED_SUFFIX, "true"); + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SWAGGER_SUFFIX, "false"); + + props.setProperty(svcpfx + PolicyEndPointProperties.PROPERTY_HTTP_SERIALIZATION_PROVIDER, + GsonMessageBodyHandler.class.getName()); + return props; + } + + /** + * Initializes {@link #oper}. + * + * @param clientName name of the client which it should use + */ + private void initOper(String clientName) { + oper = new MyOper(); + + HttpParams params = HttpParams.builder().clientName(clientName).path("request").build(); + Map<String, Object> mapParams = Util.translateToMap(OPERATION, params); + oper.configure(mapParams); + oper.start(); + } + + /** + * Runs the operation. + * + * @return the outcome of the operation, or {@code null} if it does not complete in + * time + */ + private OperationOutcome runOperation() throws InterruptedException, ExecutionException, TimeoutException { + ControlLoopOperationParams params = + ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build(); + + CompletableFuture<OperationOutcome> future = oper.startOperationAsync(params, 1, params.makeOutcome()); + + return future.get(5, TimeUnit.SECONDS); + } + + + private class MyOper extends SdncOperator { + + /** + * Set to {@code true} to cause the decoder to throw an exception. + */ + @Setter + private boolean decodeFailure = false; + + public MyOper() { + super(ACTOR, OPERATION); + } + + protected HttpClient getTheClient() { + return getClient(); + } + + @Override + protected SdncRequest constructRequest(ControlLoopEventContext context) { + SdncRequest request = new SdncRequest(); + + SdncHealRequest heal = new SdncHealRequest(); + request.setHealRequest(heal); + + return request; + } + + @Override + protected StandardCoder makeDecoder() { + if (decodeFailure) { + // return a coder that throws exceptions when decode() is invoked + return new StandardCoder() { + @Override + public <T> T decode(String json, Class<T> clazz) throws CoderException { + throw new CoderException(EXPECTED_EXCEPTION); + } + }; + + } else { + return super.makeDecoder(); + } + } + } + + /** + * SDNC Simulator. + */ + @Path("/sdnc") + @Produces(MEDIA_TYPE_APPLICATION_JSON) + public static class Server { + + /** + * Generates a response. + * + * @param request incoming request + * @return resulting response + */ + @POST + @Path("/request") + @Consumes(value = {MEDIA_TYPE_APPLICATION_JSON}) + public Response postRequest(SdncRequest request) { + + SdncResponse response = new SdncResponse(); + response.setResponseOutput(output); + + return Response.status(Status.OK).entity(response).build(); + } + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/test/resources/bod.json b/models-interactions/model-actors/actor.sdnc/src/test/resources/bod.json new file mode 100644 index 000000000..8c60bbdd8 --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/test/resources/bod.json @@ -0,0 +1,32 @@ +{ + "input": { + "sdnc-request-header": { + "svc-request-id": "076b243e-9236-4409-973d-dd318dcab3e9", + "svc-action": "update" + }, + "request-information": { + "request-action": "SdwanBandwidthChange" + }, + "service-information": { + "service-instance-id": "my-service" + }, + "vnf-information": { + "vnf-id": "my-vnf" + }, + "vf-module-information": { + "vf-module-id": "" + }, + "vf-module-request-input": { + "vf-module-input-parameters": { + "param": [ + { + "name": "bandwidth" + }, + { + "name": "bandwidth-change-time" + } + ] + } + } + } +} diff --git a/models-interactions/model-actors/actor.sdnc/src/test/resources/reroute.json b/models-interactions/model-actors/actor.sdnc/src/test/resources/reroute.json new file mode 100644 index 000000000..da70a55a3 --- /dev/null +++ b/models-interactions/model-actors/actor.sdnc/src/test/resources/reroute.json @@ -0,0 +1,17 @@ +{ + "input": { + "sdnc-request-header": { + "svc-request-id": "66087b39-8606-4367-9ac8-bdf64e162f29", + "svc-action": "reoptimize" + }, + "request-information": { + "request-action": "ReoptimizeSOTNInstance" + }, + "service-information": { + "service-instance-id": "my-service" + }, + "network-information": { + "network-id": "my-network" + } + } +} diff --git a/models-interactions/model-actors/actor.sdnr/src/main/java/org/onap/policy/controlloop/actor/sdnr/SdnrActorServiceProvider.java b/models-interactions/model-actors/actor.sdnr/src/main/java/org/onap/policy/controlloop/actor/sdnr/SdnrActorServiceProvider.java index 4367f54c5..0919779a5 100644 --- a/models-interactions/model-actors/actor.sdnr/src/main/java/org/onap/policy/controlloop/actor/sdnr/SdnrActorServiceProvider.java +++ b/models-interactions/model-actors/actor.sdnr/src/main/java/org/onap/policy/controlloop/actor/sdnr/SdnrActorServiceProvider.java @@ -3,7 +3,7 @@ * SdnrActorServiceProvider * ================================================================================ * Copyright (C) 2018 Wipro Limited Intellectual Property. All rights reserved. - * 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. @@ -23,14 +23,12 @@ package org.onap.policy.controlloop.actor.sdnr; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - import java.util.Collections; import java.util.List; - import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.ControlLoopResponse; import org.onap.policy.controlloop.VirtualControlLoopEvent; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; import org.onap.policy.controlloop.policy.Policy; import org.onap.policy.controlloop.policy.PolicyResult; import org.onap.policy.sdnr.PciCommonHeader; @@ -39,11 +37,12 @@ import org.onap.policy.sdnr.PciRequestWrapper; import org.onap.policy.sdnr.PciResponse; import org.onap.policy.sdnr.PciResponseCode; import org.onap.policy.sdnr.PciResponseWrapper; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SdnrActorServiceProvider implements Actor { +public class SdnrActorServiceProvider extends ActorImpl { + + private static final String NAME = "SDNR"; public static class Pair<A, B> { public final A result; @@ -81,9 +80,13 @@ public class SdnrActorServiceProvider implements Actor { private static final ImmutableMap<String, List<String>> payloads = new ImmutableMap.Builder<String, List<String>>() .put(RECIPE_MODIFY, ImmutableList.of(SDNR_REQUEST_PARAMS, SDNR_CONFIG_PARAMS)).build(); + public SdnrActorServiceProvider() { + super(NAME); + } + @Override public String actor() { - return "SDNR"; + return NAME; } @Override @@ -275,7 +278,7 @@ public class SdnrActorServiceProvider implements Actor { /* The ControlLoop response determined from the SDNR Response and input event. */ ControlLoopResponse clRsp = new ControlLoopResponse(); clRsp.setPayload(sdnrResponse.getPayload()); - clRsp.setFrom("SDNR"); + clRsp.setFrom(NAME); clRsp.setTarget("DCAE"); clRsp.setClosedLoopControlName(event.getClosedLoopControlName()); clRsp.setPolicyName(event.getPolicyName()); diff --git a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorServiceProvider.java b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorServiceProvider.java index b85307547..a743f49c0 100644 --- a/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorServiceProvider.java +++ b/models-interactions/model-actors/actor.so/src/main/java/org/onap/policy/controlloop/actor/so/SoActorServiceProvider.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * SOActorServiceProvider * ================================================================================ - * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,7 @@ import org.onap.aai.domain.yang.Tenant; import org.onap.policy.aai.AaiCqResponse; import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; import org.onap.policy.controlloop.policy.Policy; import org.onap.policy.so.SoCloudConfiguration; import org.onap.policy.so.SoManager; @@ -51,7 +51,7 @@ import org.onap.policy.so.util.Serialization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class SoActorServiceProvider implements Actor { +public class SoActorServiceProvider extends ActorImpl { private static final Logger logger = LoggerFactory.getLogger(SoActorServiceProvider.class); private static final String TENANT_NOT_FOUND = "Tenant Item not found in AAI response {}"; @@ -89,6 +89,10 @@ public class SoActorServiceProvider implements Actor { private static String lastServiceItemServiceInstanceId; private static String lastVfModuleItemVfModuleInstanceId; + public SoActorServiceProvider() { + super(SO_ACTOR); + } + @Override public String actor() { return SO_ACTOR; diff --git a/models-interactions/model-actors/actor.vfc/src/main/java/org/onap/policy/controlloop/actor/vfc/VfcActorServiceProvider.java b/models-interactions/model-actors/actor.vfc/src/main/java/org/onap/policy/controlloop/actor/vfc/VfcActorServiceProvider.java index 1ca379f71..8d560ef3c 100644 --- a/models-interactions/model-actors/actor.vfc/src/main/java/org/onap/policy/controlloop/actor/vfc/VfcActorServiceProvider.java +++ b/models-interactions/model-actors/actor.vfc/src/main/java/org/onap/policy/controlloop/actor/vfc/VfcActorServiceProvider.java @@ -1,7 +1,7 @@ /*- * ============LICENSE_START======================================================= * Copyright (C) 2017-2018 Intel Corp. All rights reserved. - * Modifications Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved. + * Modifications Copyright (C) 2018-2020 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,21 +22,19 @@ package org.onap.policy.controlloop.actor.vfc; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; - import java.util.Collections; import java.util.List; - import org.onap.policy.aai.AaiCqResponse; import org.onap.policy.controlloop.ControlLoopOperation; import org.onap.policy.controlloop.VirtualControlLoopEvent; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; import org.onap.policy.controlloop.policy.Policy; import org.onap.policy.vfc.VfcHealActionVmInfo; import org.onap.policy.vfc.VfcHealAdditionalParams; import org.onap.policy.vfc.VfcHealRequest; import org.onap.policy.vfc.VfcRequest; -public class VfcActorServiceProvider implements Actor { +public class VfcActorServiceProvider extends ActorImpl { private static final String GENERIC_VNF_ID = "generic-vnf.vnf-id"; // Strings for VFC Actor @@ -52,6 +50,10 @@ public class VfcActorServiceProvider implements Actor { private static final ImmutableMap<String, List<String>> targets = new ImmutableMap.Builder<String, List<String>>().put(RECIPE_RESTART, ImmutableList.of(TARGET_VM)).build(); + public VfcActorServiceProvider() { + super(VFC_ACTOR); + } + @Override public String actor() { return VFC_ACTOR; diff --git a/models-interactions/model-actors/actorServiceProvider/pom.xml b/models-interactions/model-actors/actorServiceProvider/pom.xml index ee3a924f8..54b13f0ed 100644 --- a/models-interactions/model-actors/actorServiceProvider/pom.xml +++ b/models-interactions/model-actors/actorServiceProvider/pom.xml @@ -18,22 +18,47 @@ ============LICENSE_END========================================================= --> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> - <parent> - <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> - <artifactId>model-actors</artifactId> - <version>2.2.1-SNAPSHOT</version> - </parent> + <parent> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>model-actors</artifactId> + <version>2.2.1-SNAPSHOT</version> + </parent> - <artifactId>actorServiceProvider</artifactId> + <artifactId>actorServiceProvider</artifactId> - <dependencies> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - </dependencies> + <dependencies> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>aai</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>events</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.common</groupId> + <artifactId>policy-endpoints</artifactId> + <version>${policy.common.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-mockito2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> </project> diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java index 809936146..2886b1feb 100644 --- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/ActorService.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * ActorService * ================================================================================ - * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2018, 2020 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,25 +21,55 @@ package org.onap.policy.controlloop.actorserviceprovider; -import com.google.common.collect.ImmutableList; - -import java.util.Iterator; +import com.google.common.collect.ImmutableMap; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import java.util.ServiceLoader; - +import java.util.Set; +import org.onap.policy.common.parameters.BeanValidationResult; +import org.onap.policy.controlloop.actorserviceprovider.impl.StartConfigPartial; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ActorService { - +/** + * Service that manages a set of actors. To use the service, first invoke + * {@link #configure(Map)} to configure all of the actors, and then invoke + * {@link #start()} to start all of the actors. When finished using the actor service, + * invoke {@link #stop()} or {@link #shutdown()}. + */ +public class ActorService extends StartConfigPartial<Map<String, Object>> { private static final Logger logger = LoggerFactory.getLogger(ActorService.class); - private static ActorService service; - // USed to load actors - private final ServiceLoader<Actor> loader; + private final Map<String, Actor> name2actor; + + private static class LazyHolder { + static final ActorService INSTANCE = new ActorService(); + } + + /** + * Constructs the object and loads the list of actors. + */ + protected ActorService() { + super("actors"); + + Map<String, Actor> map = new HashMap<>(); + + for (Actor newActor : loadActors()) { + map.compute(newActor.getName(), (name, existingActor) -> { + if (existingActor == null) { + return newActor; + } + + logger.warn("duplicate actor names for {}: {}, ignoring {}", name, + existingActor.getClass().getSimpleName(), newActor.getClass().getSimpleName()); + return existingActor; + }); + } - private ActorService() { - loader = ServiceLoader.load(Actor.class); + name2actor = ImmutableMap.copyOf(map); } /** @@ -47,27 +77,115 @@ public class ActorService { * * @return the instance */ - public static synchronized ActorService getInstance() { - if (service == null) { - service = new ActorService(); + public static ActorService getInstance() { + return LazyHolder.INSTANCE; + } + + /** + * Gets a particular actor. + * + * @param name name of the actor of interest + * @return the desired actor + * @throws IllegalArgumentException if no actor by the given name exists + */ + public Actor getActor(String name) { + Actor actor = name2actor.get(name); + if (actor == null) { + throw new IllegalArgumentException("unknown actor " + name); } - return service; + + return actor; } /** - * Get the actors. + * Gets the actors. * * @return the actors */ - public ImmutableList<Actor> actors() { - Iterator<Actor> iter = loader.iterator(); - logger.debug("returning actors"); - while (iter.hasNext()) { - if (logger.isDebugEnabled()) { - logger.debug("Got {}", iter.next().actor()); + public Collection<Actor> getActors() { + return name2actor.values(); + } + + /** + * Gets the names of the actors. + * + * @return the actor names + */ + public Set<String> getActorNames() { + return name2actor.keySet(); + } + + @Override + protected void doConfigure(Map<String, Object> parameters) { + logger.info("configuring actors"); + + BeanValidationResult valres = new BeanValidationResult("ActorService", parameters); + + for (Actor actor : name2actor.values()) { + String actorName = actor.getName(); + Map<String, Object> subparams = Util.translateToMap(actorName, parameters.get(actorName)); + + if (subparams != null) { + + try { + actor.configure(subparams); + + } catch (ParameterValidationRuntimeException e) { + logger.warn("failed to configure actor {}", actorName, e); + valres.addResult(e.getResult()); + + } catch (RuntimeException e) { + logger.warn("failed to configure actor {}", actorName, e); + } + + } else if (actor.isConfigured()) { + logger.warn("missing configuration parameters for actor {}; using previous parameters", actorName); + + } else { + logger.warn("missing configuration parameters for actor {}; actor cannot be started", actorName); } } - return ImmutableList.copyOf(loader.iterator()); + if (!valres.isValid() && logger.isWarnEnabled()) { + logger.warn("actor services validation errors:\n{}", valres.getResult()); + } + } + + @Override + protected void doStart() { + logger.info("starting actors"); + + for (Actor actor : name2actor.values()) { + if (actor.isConfigured()) { + Util.runFunction(actor::start, "failed to start actor {}", actor.getName()); + + } else { + logger.warn("not starting unconfigured actor {}", actor.getName()); + } + } + } + + @Override + protected void doStop() { + logger.info("stopping actors"); + name2actor.values() + .forEach(actor -> Util.runFunction(actor::stop, "failed to stop actor {}", actor.getName())); + } + + @Override + protected void doShutdown() { + logger.info("shutting down actors"); + + // @formatter:off + name2actor.values().forEach( + actor -> Util.runFunction(actor::shutdown, "failed to shutdown actor {}", actor.getName())); + + // @formatter:on + } + + // the following methods may be overridden by junit tests + + protected Iterable<Actor> loadActors() { + return ServiceLoader.load(Actor.class); } } diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java new file mode 100644 index 000000000..d78403809 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandler.java @@ -0,0 +1,119 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import javax.ws.rs.client.InvocationCallback; +import lombok.AccessLevel; +import lombok.Getter; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handler for a <i>single</i> asynchronous response. + * + * @param <T> response type + */ +@Getter +public abstract class AsyncResponseHandler<T> implements InvocationCallback<T> { + + private static final Logger logger = LoggerFactory.getLogger(AsyncResponseHandler.class); + + @Getter(AccessLevel.NONE) + private final PipelineControllerFuture<OperationOutcome> result = new PipelineControllerFuture<>(); + private final ControlLoopOperationParams params; + private final OperationOutcome outcome; + + /** + * Constructs the object. + * + * @param params operation parameters + * @param outcome outcome to be populated based on the response + */ + public AsyncResponseHandler(ControlLoopOperationParams params, OperationOutcome outcome) { + this.params = params; + this.outcome = outcome; + } + + /** + * Handles the given future, arranging to cancel it when the response is received. + * + * @param future future to be handled + * @return a future to be used to cancel or wait for the response + */ + public CompletableFuture<OperationOutcome> handle(Future<T> future) { + result.add(future); + return result; + } + + /** + * Invokes {@link #doComplete()} and then completes "this" with the returned value. + */ + @Override + public void completed(T rawResponse) { + try { + logger.trace("{}.{}: response completed for {}", params.getActor(), params.getOperation(), + params.getRequestId()); + result.complete(doComplete(rawResponse)); + + } catch (RuntimeException e) { + logger.trace("{}.{}: response handler threw an exception for {}", params.getActor(), params.getOperation(), + params.getRequestId()); + result.completeExceptionally(e); + } + } + + /** + * Invokes {@link #doFailed()} and then completes "this" with the returned value. + */ + @Override + public void failed(Throwable throwable) { + try { + logger.trace("{}.{}: response failure for {}", params.getActor(), params.getOperation(), + params.getRequestId()); + result.complete(doFailed(throwable)); + + } catch (RuntimeException e) { + logger.trace("{}.{}: response failure handler threw an exception for {}", params.getActor(), + params.getOperation(), params.getRequestId()); + result.completeExceptionally(e); + } + } + + /** + * Completes the processing of a response. + * + * @param rawResponse raw response that was received + * @return the outcome + */ + protected abstract OperationOutcome doComplete(T rawResponse); + + /** + * Handles a response exception. + * + * @param thrown exception that was thrown + * @return the outcome + */ + protected abstract OperationOutcome doFailed(Throwable thrown); +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/CallbackManager.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/CallbackManager.java new file mode 100644 index 000000000..7d7c1d902 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/CallbackManager.java @@ -0,0 +1,84 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import java.time.Instant; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Manager for "start" and "end" callbacks. + */ +public class CallbackManager implements Runnable { + private final AtomicReference<Instant> startTime = new AtomicReference<>(); + private final AtomicReference<Instant> endTime = new AtomicReference<>(); + + /** + * Determines if the "start" callback can be invoked. If so, it sets the + * {@link #startTime} to the current time. + * + * @return {@code true} if the "start" callback can be invoked, {@code false} + * otherwise + */ + public boolean canStart() { + return startTime.compareAndSet(null, Instant.now()); + } + + /** + * Determines if the "end" callback can be invoked. If so, it sets the + * {@link #endTime} to the current time. + * + * @return {@code true} if the "end" callback can be invoked, {@code false} + * otherwise + */ + public boolean canEnd() { + return endTime.compareAndSet(null, Instant.now()); + } + + /** + * Gets the start time. + * + * @return the start time, or {@code null} if {@link #canStart()} has not been + * invoked yet. + */ + public Instant getStartTime() { + return startTime.get(); + } + + /** + * Gets the end time. + * + * @return the end time, or {@code null} if {@link #canEnd()} has not been invoked + * yet. + */ + public Instant getEndTime() { + return endTime.get(); + } + + /** + * Prevents further callbacks from being executed by setting {@link #startTime} + * and {@link #endTime}. + */ + @Override + public void run() { + canStart(); + canEnd(); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentString.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentString.java new file mode 100644 index 000000000..b7a9a53ad --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentString.java @@ -0,0 +1,65 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import lombok.AllArgsConstructor; + +/** + * Object whose {@link #toString()} method invokes {@link Object#toString()} on another + * object, on-demand. This assumes that the other object's method returns an object + * identifier. This is typically used to include an object's identifier in a log message. + */ +@AllArgsConstructor +public class DelayedIdentString { + /** + * String to return for null objects or null object identifiers. + */ + public static final String NULL_STRING = "null"; + + private final Object object; + + /** + * Gets the object's identifier, after stripping anything appearing before '@'. + */ + @Override + public String toString() { + if (object == null) { + return NULL_STRING; + } + + String ident = objectToString(); + if (ident == null) { + return NULL_STRING; + } + + int index = ident.indexOf('@'); + return (index > 0 ? ident.substring(index) : ident); + } + + /** + * Invokes the object's {@link Object#toString()} method. + * + * @return the output from the object's {@link Object#toString()} method + */ + protected String objectToString() { + return object.toString(); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/OperationOutcome.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/OperationOutcome.java new file mode 100644 index 000000000..6b0924807 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/OperationOutcome.java @@ -0,0 +1,116 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import java.time.Instant; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.PolicyResult; + +/** + * Outcome from an operation. Objects of this type are passed from one stage to the next. + */ +@Data +@NoArgsConstructor +public class OperationOutcome { + private String actor; + private String operation; + private String target; + private Instant start; + private Instant end; + private String subRequestId; + private PolicyResult result = PolicyResult.SUCCESS; + private String message; + + /** + * Copy constructor. + * + * @param source source object from which to copy + */ + public OperationOutcome(OperationOutcome source) { + this.actor = source.actor; + this.operation = source.operation; + this.target = source.target; + this.start = source.start; + this.end = source.end; + this.subRequestId = source.subRequestId; + this.result = source.result; + this.message = source.message; + } + + /** + * Creates a {@link ControlLoopOperation}, populating all fields with the values from + * this object. Sets the outcome field to the string representation of this object's + * outcome. + * + * @return + */ + public ControlLoopOperation toControlLoopOperation() { + ControlLoopOperation clo = new ControlLoopOperation(); + + clo.setActor(actor); + clo.setOperation(operation); + clo.setTarget(target); + clo.setStart(start); + clo.setEnd(end); + clo.setSubRequestId(subRequestId); + clo.setOutcome(result.toString()); + clo.setMessage(message); + + return clo; + } + + /** + * Determines if this outcome is for the given actor and operation. + * + * @param actor actor name + * @param operation operation name + * @return {@code true} if this outcome is for the given actor and operation + */ + public boolean isFor(@NonNull String actor, @NonNull String operation) { + // do the operation check first, as it's most likely to be unique + return (operation.equals(this.operation) && actor.equals(this.actor)); + } + + /** + * Determines if an outcome is for the given actor and operation. + * + * @param outcome outcome to be examined, or {@code null} + * @param actor actor name + * @param operation operation name + * @return {@code true} if this outcome is for the given actor and operation, + * {@code false} it is {@code null} or not for the actor/operation + */ + public static boolean isFor(OperationOutcome outcome, String actor, String operation) { + return (outcome != null && outcome.isFor(actor, operation)); + } + + /** + * Sets the result. + * + * @param result new result + */ + public void setResult(@NonNull PolicyResult result) { + this.result = result; + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java new file mode 100644 index 000000000..c09460e34 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Operator.java @@ -0,0 +1,57 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.onap.policy.common.capabilities.Configurable; +import org.onap.policy.common.capabilities.Startable; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; + +/** + * This is the service interface for defining an Actor operation used in Control Loop + * Operational Policies for performing actions on runtime entities. + */ +public interface Operator extends Startable, Configurable<Map<String, Object>> { + + /** + * Gets the name of the associated actor. + * + * @return the name of the associated actor + */ + String getActorName(); + + /** + * Gets the name of the operation. + * + * @return the operation name + */ + String getName(); + + /** + * Called by enforcement PDP engine to start the operation. As part of the operation, + * it invokes the "start" and "complete" call-backs found within the parameters. + * + * @param params parameters needed to start the operation + * @return a future that can be used to cancel or await the result of the operation + */ + CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params); +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java new file mode 100644 index 000000000..c3ddd17f3 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/Util.java @@ -0,0 +1,193 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.utils.NetLoggerUtil; +import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Actor utilities. + */ +public class Util { + private static final Logger logger = LoggerFactory.getLogger(Util.class); + + private Util() { + // do nothing + } + + /** + * Extracts an object's identity by invoking {@link Object#toString()} and returning + * the portion starting with "@". Extraction is done on-demand, when toString() is + * called on the result. This is typically used when logging. + * + * @param object object whose identity is to be extracted + * @return an object that will extract the source object's identity when this object's + * toString() method is called + */ + public static Object ident(Object object) { + return new DelayedIdentString(object); + } + + /** + * Logs a REST request. If the request is not of type, String, then it attempts to + * pretty-print it into JSON before logging. + * + * @param url request URL + * @param request request to be logged + */ + public static <T> void logRestRequest(String url, T request) { + logRestRequest(new StandardCoder(), url, request); + } + + /** + * Logs a REST request. If the request is not of type, String, then it attempts to + * pretty-print it into JSON before logging. + * + * @param coder coder to be used to pretty-print the request + * @param url request URL + * @param request request to be logged + */ + protected static <T> void logRestRequest(Coder coder, String url, T request) { + String json; + try { + if (request instanceof String) { + json = request.toString(); + } else { + json = coder.encode(request, true); + } + + } catch (CoderException e) { + logger.warn("cannot pretty-print request", e); + json = request.toString(); + } + + NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, url, json); + logger.info("[OUT|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json); + } + + /** + * Logs a REST response. If the response is not of type, String, then it attempts to + * pretty-print it into JSON before logging. + * + * @param url request URL + * @param response response to be logged + */ + public static <T> void logRestResponse(String url, T response) { + logRestResponse(new StandardCoder(), url, response); + } + + /** + * Logs a REST response. If the request is not of type, String, then it attempts to + * pretty-print it into JSON before logging. + * + * @param coder coder to be used to pretty-print the response + * @param url request URL + * @param response response to be logged + */ + protected static <T> void logRestResponse(Coder coder, String url, T response) { + String json; + try { + if (response == null) { + json = null; + } else if (response instanceof String) { + json = response.toString(); + } else { + json = coder.encode(response, true); + } + + } catch (CoderException e) { + logger.warn("cannot pretty-print response", e); + json = response.toString(); + } + + NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, url, json); + logger.info("[IN|{}|{}|]{}{}", CommInfrastructure.REST, url, NetLoggerUtil.SYSTEM_LS, json); + } + + /** + * Runs a function and logs a message if it throws an exception. Does <i>not</i> + * re-throw the exception. + * + * @param function function to be run + * @param exceptionMessage message to log if an exception is thrown + * @param exceptionArgs arguments to be passed to the logger + */ + public static void runFunction(Runnable function, String exceptionMessage, Object... exceptionArgs) { + try { + function.run(); + + } catch (RuntimeException ex) { + // create a new array containing the original arguments plus the exception + Object[] allArgs = Arrays.copyOf(exceptionArgs, exceptionArgs.length + 1); + allArgs[exceptionArgs.length] = ex; + + logger.warn(exceptionMessage, allArgs); + } + } + + /** + * Translates parameters from one class to another, typically from a Map to a POJO or + * vice versa. + * + * @param identifier identifier of the actor/operation being translated; used to build + * an exception message + * @param source source object to be translated + * @param clazz target class + * @return the translated object + */ + public static <T> T translate(String identifier, Object source, Class<T> clazz) { + Coder coder = new StandardCoder(); + + try { + String json = coder.encode(source); + return coder.decode(json, clazz); + + } catch (CoderException | RuntimeException e) { + throw new IllegalArgumentException("cannot translate parameters for " + identifier, e); + } + } + + /** + * Translates parameters to a Map. + * + * @param identifier identifier of the actor/operation being translated; used to build + * an exception message + * @param source source parameters + * @return the parameters, as a Map + */ + @SuppressWarnings("unchecked") + public static Map<String, Object> translateToMap(String identifier, Object source) { + if (source == null) { + return null; + } + + return translate(identifier, source, LinkedHashMap.class); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java new file mode 100644 index 000000000..cd4d2570f --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContext.java @@ -0,0 +1,103 @@ +/*- + * ============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.controlloop; + +import java.io.Serializable; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; +import org.onap.policy.controlloop.VirtualControlLoopEvent; + +/** + * Context associated with a control loop event. + */ +@Getter +@Setter +public class ControlLoopEventContext implements Serializable { + private static final long serialVersionUID = 1L; + + + private final VirtualControlLoopEvent event; + + /** + * Enrichment data extracted from the event. Never {@code null}, though it may be + * immutable. + */ + private final Map<String, String> enrichment; + + @Getter(AccessLevel.NONE) + @Setter(AccessLevel.NONE) + private Map<String, Serializable> properties = new ConcurrentHashMap<>(); + + /** + * Request ID extracted from the event, or a generated value if the event has no + * request id; never {@code null}. + */ + private final UUID requestId; + + + /** + * Constructs the object. + * + * @param event event with which this is associated + */ + public ControlLoopEventContext(@NonNull VirtualControlLoopEvent event) { + this.event = event; + this.requestId = (event.getRequestId() != null ? event.getRequestId() : UUID.randomUUID()); + this.enrichment = (event.getAai() != null ? event.getAai() : Map.of()); + } + + /** + * Determines if the context contains a property. + * + * @param name name of the property of interest + * @return {@code true} if the context contains the property, {@code false} otherwise + */ + public boolean contains(String name) { + return properties.containsKey(name); + } + + /** + * Gets a property, casting it to the desired type. + * + * @param <T> desired type + * @param name name of the property whose value is to be retrieved + * @return the property's value, or {@code null} if it does not yet have a value + */ + @SuppressWarnings("unchecked") + public <T> T getProperty(String name) { + return (T) properties.get(name); + } + + /** + * Sets a property's value. + * + * @param name property name + * @param value new property value + */ + public void setProperty(String name, Serializable value) { + properties.put(name, value); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java new file mode 100644 index 000000000..d7f322e8a --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImpl.java @@ -0,0 +1,235 @@ +/*- + * ============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.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import org.onap.policy.common.parameters.BeanValidationResult; +import org.onap.policy.controlloop.actorserviceprovider.Operator; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; +import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implementation of an actor. + */ +public class ActorImpl extends StartConfigPartial<Map<String, Object>> implements Actor { + private static final Logger logger = LoggerFactory.getLogger(ActorImpl.class); + + /** + * Maps a name to an operator. + */ + private final Map<String, Operator> name2operator = new ConcurrentHashMap<>(); + + /** + * Constructs the object. + * + * @param name actor name + */ + public ActorImpl(String name) { + super(name); + } + + /** + * Adds an operator supported by this actor. + * + * @param operator operation to be added + */ + protected synchronized void addOperator(Operator operator) { + /* + * This method is "synchronized" to prevent the state from changing while the + * operator is added. The map, itself, does not need synchronization as it's a + * concurrent map. + */ + + if (isConfigured()) { + throw new IllegalStateException("attempt to set operators on a configured actor: " + getName()); + } + + name2operator.compute(operator.getName(), (opName, existingOp) -> { + if (existingOp == null) { + return operator; + } + + logger.warn("duplicate names for actor operation {}.{}: {}, ignoring {}", getName(), opName, + existingOp.getClass().getSimpleName(), operator.getClass().getSimpleName()); + return existingOp; + }); + } + + @Override + public String getName() { + return getFullName(); + } + + @Override + public Operator getOperator(String name) { + Operator operator = name2operator.get(name); + if (operator == null) { + throw new IllegalArgumentException("unknown operation " + getName() + "." + name); + } + + return operator; + } + + @Override + public Collection<Operator> getOperators() { + return name2operator.values(); + } + + @Override + public Set<String> getOperationNames() { + return name2operator.keySet(); + } + + /** + * For each operation, it looks for a set of parameters by the same name and, if + * found, configures the operation with the parameters. + */ + @Override + protected void doConfigure(Map<String, Object> parameters) { + final String actorName = getName(); + logger.info("configuring operations for actor {}", actorName); + + BeanValidationResult valres = new BeanValidationResult(actorName, parameters); + + // function that creates operator-specific parameters, given the operation name + Function<String, Map<String, Object>> opParamsMaker = makeOperatorParameters(parameters); + + for (Operator operator : name2operator.values()) { + String operName = operator.getName(); + Map<String, Object> subparams = opParamsMaker.apply(operName); + + if (subparams != null) { + + try { + operator.configure(subparams); + + } catch (ParameterValidationRuntimeException e) { + logger.warn("failed to configure operation {}.{}", actorName, operName, e); + valres.addResult(e.getResult()); + + } catch (RuntimeException e) { + logger.warn("failed to configure operation {}.{}", actorName, operName, e); + } + + } else if (operator.isConfigured()) { + logger.warn("missing configuration parameters for operation {}.{}; using previous parameters", + actorName, operName); + + } else { + logger.warn("missing configuration parameters for operation {}.{}; operation cannot be started", + actorName, operName); + } + } + } + + /** + * Extracts the operator parameters from the actor parameters, for a given operator. + * This method assumes each operation has its own set of parameters. + * + * @param actorParameters actor parameters + * @return a function to extract the operator parameters from the actor parameters. + * Note: this function may return {@code null} if there are no parameters for + * the given operation name + */ + protected Function<String, Map<String, Object>> makeOperatorParameters(Map<String, Object> actorParameters) { + + return operName -> Util.translateToMap(getName() + "." + operName, actorParameters.get(operName)); + } + + /** + * Starts each operation. + */ + @Override + protected void doStart() { + final String actorName = getName(); + logger.info("starting operations for actor {}", actorName); + + for (Operator oper : name2operator.values()) { + if (oper.isConfigured()) { + Util.runFunction(oper::start, "failed to start operation {}.{}", actorName, oper.getName()); + + } else { + logger.warn("not starting unconfigured operation {}.{}", actorName, oper.getName()); + } + } + } + + /** + * Stops each operation. + */ + @Override + protected void doStop() { + final String actorName = getName(); + logger.info("stopping operations for actor {}", actorName); + + // @formatter:off + name2operator.values().forEach( + oper -> Util.runFunction(oper::stop, "failed to stop operation {}.{}", actorName, oper.getName())); + // @formatter:on + } + + /** + * Shuts down each operation. + */ + @Override + protected void doShutdown() { + final String actorName = getName(); + logger.info("shutting down operations for actor {}", actorName); + + // @formatter:off + name2operator.values().forEach(oper -> Util.runFunction(oper::shutdown, + "failed to shutdown operation {}.{}", actorName, oper.getName())); + // @formatter:on + } + + // TODO old code: remove lines down to **HERE** + + @Override + public String actor() { + return null; + } + + @Override + public List<String> recipes() { + return Collections.emptyList(); + } + + @Override + public List<String> recipeTargets(String recipe) { + return Collections.emptyList(); + } + + @Override + public List<String> recipePayloads(String recipe) { + return Collections.emptyList(); + } + + // **HERE** +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActor.java new file mode 100644 index 000000000..28b7b3924 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActor.java @@ -0,0 +1,58 @@ +/*- + * ============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.function.Function; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpActorParams; + +/** + * Actor that uses HTTP, where the only additional property that an operator needs is a + * URL. The actor's parameters must be an {@link HttpActorParams} and its operator + * parameters are expected to be an {@link HttpParams}. + */ +public class HttpActor extends ActorImpl { + + /** + * Constructs the object. + * + * @param name actor's name + */ + public HttpActor(String name) { + super(name); + } + + /** + * Translates the parameters to an {@link HttpActorParams} and then creates a function + * that will extract operator-specific parameters. + */ + @Override + protected Function<String, Map<String, Object>> makeOperatorParameters(Map<String, Object> actorParameters) { + String actorName = getName(); + + // @formatter:off + return Util.translate(actorName, actorParameters, HttpActorParams.class) + .doValidation(actorName) + .makeOperationParameters(actorName); + // @formatter:on + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java new file mode 100644 index 000000000..566492907 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperator.java @@ -0,0 +1,84 @@ +/*- + * ============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 lombok.AccessLevel; +import lombok.Getter; +import org.onap.policy.common.endpoints.http.client.HttpClient; +import org.onap.policy.common.endpoints.http.client.HttpClientFactory; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; + +/** + * Operator that uses HTTP. The operator's parameters must be a {@link HttpParams}. + */ +public class HttpOperator extends OperatorPartial { + + @Getter(AccessLevel.PROTECTED) + private HttpClient client; + + @Getter + private long timeoutSec; + + /** + * URI path for this particular operation. + */ + @Getter + private String path; + + + /** + * Constructs the object. + * + * @param actorName name of the actor with which this operator is associated + * @param name operation name + */ + public HttpOperator(String actorName, String name) { + super(actorName, name); + } + + /** + * Translates the parameters to an {@link HttpParams} and then extracts the relevant + * values. + */ + @Override + protected void doConfigure(Map<String, Object> parameters) { + HttpParams params = Util.translate(getFullName(), parameters, HttpParams.class); + ValidationResult result = params.validate(getFullName()); + if (!result.isValid()) { + throw new ParameterValidationRuntimeException("invalid parameters", result); + } + + client = getClientFactory().get(params.getClientName()); + path = params.getPath(); + timeoutSec = params.getTimeoutSec(); + } + + // these may be overridden by junits + + protected HttpClientFactory getClientFactory() { + return HttpClientFactoryInstance.getClientFactory(); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java new file mode 100644 index 000000000..df5258d71 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartial.java @@ -0,0 +1,845 @@ +/*- + * ============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.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.function.BiConsumer; +import java.util.function.Function; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.actorserviceprovider.CallbackManager; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Operator; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Partial implementation of an operator. In general, it's preferable that subclasses + * would override + * {@link #startOperationAsync(ControlLoopOperationParams, int, OperationOutcome) + * startOperationAsync()}. However, if that proves to be too difficult, then they can + * simply override {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome) + * doOperation()}. In addition, if the operation requires any preprocessor steps, the + * subclass may choose to override + * {@link #startPreprocessorAsync(ControlLoopOperationParams) startPreprocessorAsync()}. + * <p/> + * The futures returned by the methods within this class can be canceled, and will + * propagate the cancellation to any subtasks. Thus it is also expected that any futures + * returned by overridden methods will do the same. Of course, if a class overrides + * {@link #doOperation(ControlLoopOperationParams, int, OperationOutcome) doOperation()}, + * then there's little that can be done to cancel that particular operation. + */ +public abstract class OperatorPartial extends StartConfigPartial<Map<String, Object>> implements Operator { + + private static final Logger logger = LoggerFactory.getLogger(OperatorPartial.class); + + /** + * Executor to be used for tasks that may perform blocking I/O. The default executor + * simply launches a new thread for each command that is submitted to it. + * <p/> + * May be overridden by junit tests. + */ + @Getter(AccessLevel.PROTECTED) + @Setter(AccessLevel.PROTECTED) + private Executor blockingExecutor = command -> { + Thread thread = new Thread(command); + thread.setDaemon(true); + thread.start(); + }; + + @Getter + private final String actorName; + + @Getter + private final String name; + + /** + * Constructs the object. + * + * @param actorName name of the actor with which this operator is associated + * @param name operation name + */ + public OperatorPartial(String actorName, String name) { + super(actorName + "." + name); + this.actorName = actorName; + this.name = name; + } + + /** + * This method does nothing. + */ + @Override + protected void doConfigure(Map<String, Object> parameters) { + // do nothing + } + + /** + * This method does nothing. + */ + @Override + protected void doStart() { + // do nothing + } + + /** + * This method does nothing. + */ + @Override + protected void doStop() { + // do nothing + } + + /** + * This method does nothing. + */ + @Override + protected void doShutdown() { + // do nothing + } + + @Override + public final CompletableFuture<OperationOutcome> startOperation(ControlLoopOperationParams params) { + if (!isAlive()) { + throw new IllegalStateException("operation is not running: " + getFullName()); + } + + // allocate a controller for the entire operation + final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> preproc = startPreprocessorAsync(params); + if (preproc == null) { + // no preprocessor required - just start the operation + return startOperationAttempt(params, controller, 1); + } + + /* + * Do preprocessor first and then, if successful, start the operation. Note: + * operations create their own outcome, ignoring the outcome from any previous + * steps. + * + * Wrap the preprocessor to ensure "stop" is propagated to it. + */ + // @formatter:off + controller.wrap(preproc) + .exceptionally(fromException(params, "preprocessor of operation")) + .thenCompose(handlePreprocessorFailure(params, controller)) + .thenCompose(unusedOutcome -> startOperationAttempt(params, controller, 1)); + // @formatter:on + + return controller; + } + + /** + * Handles a failure in the preprocessor pipeline. If a failure occurred, then it + * invokes the call-backs, marks the controller complete, and returns an incomplete + * future, effectively halting the pipeline. Otherwise, it returns the outcome that it + * received. + * <p/> + * Assumes that no callbacks have been invoked yet. + * + * @param params operation parameters + * @param controller pipeline controller + * @return a function that checks the outcome status and continues, if successful, or + * indicates a failure otherwise + */ + private Function<OperationOutcome, CompletableFuture<OperationOutcome>> handlePreprocessorFailure( + ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller) { + + return outcome -> { + + if (outcome != null && isSuccess(outcome)) { + logger.trace("{}: preprocessor succeeded for {}", getFullName(), params.getRequestId()); + return CompletableFuture.completedFuture(outcome); + } + + logger.warn("preprocessor failed, discontinuing operation {} for {}", getFullName(), params.getRequestId()); + + final Executor executor = params.getExecutor(); + final CallbackManager callbacks = new CallbackManager(); + + // propagate "stop" to the callbacks + controller.add(callbacks); + + final OperationOutcome outcome2 = params.makeOutcome(); + + // TODO need a FAILURE_MISSING_DATA (e.g., A&AI) + + outcome2.setResult(PolicyResult.FAILURE_GUARD); + outcome2.setMessage(outcome != null ? outcome.getMessage() : null); + + // @formatter:off + CompletableFuture.completedFuture(outcome2) + .whenCompleteAsync(callbackStarted(params, callbacks), executor) + .whenCompleteAsync(callbackCompleted(params, callbacks), executor) + .whenCompleteAsync(controller.delayedComplete(), executor); + // @formatter:on + + return new CompletableFuture<>(); + }; + } + + /** + * Invokes the operation's preprocessor step(s) as a "future". This method simply + * returns {@code null}. + * <p/> + * This method assumes the following: + * <ul> + * <li>the operator is alive</li> + * <li>exceptions generated within the pipeline will be handled by the invoker</li> + * </ul> + * + * @param params operation parameters + * @return a function that will start the preprocessor and returns its outcome, or + * {@code null} if this operation needs no preprocessor + */ + protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) { + return null; + } + + /** + * Starts the operation attempt, with no preprocessor. When all retries complete, it + * will complete the controller. + * + * @param params operation parameters + * @param controller controller for all operation attempts + * @param attempt attempt number, typically starting with 1 + * @return a future that will return the final result of all attempts + */ + private CompletableFuture<OperationOutcome> startOperationAttempt(ControlLoopOperationParams params, + PipelineControllerFuture<OperationOutcome> controller, int attempt) { + + // propagate "stop" to the operation attempt + controller.wrap(startAttemptWithoutRetries(params, attempt)) + .thenCompose(retryOnFailure(params, controller, attempt)); + + return controller; + } + + /** + * Starts the operation attempt, without doing any retries. + * + * @param params operation parameters + * @param attempt attempt number, typically starting with 1 + * @return a future that will return the result of a single operation attempt + */ + private CompletableFuture<OperationOutcome> startAttemptWithoutRetries(ControlLoopOperationParams params, + int attempt) { + + logger.info("{}: start operation attempt {} for {}", getFullName(), attempt, params.getRequestId()); + + final Executor executor = params.getExecutor(); + final OperationOutcome outcome = params.makeOutcome(); + final CallbackManager callbacks = new CallbackManager(); + + // this operation attempt gets its own controller + final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + // propagate "stop" to the callbacks + controller.add(callbacks); + + // @formatter:off + CompletableFuture<OperationOutcome> future = CompletableFuture.completedFuture(outcome) + .whenCompleteAsync(callbackStarted(params, callbacks), executor) + .thenCompose(controller.wrap(outcome2 -> startOperationAsync(params, attempt, outcome2))); + // @formatter:on + + // handle timeouts, if specified + long timeoutMillis = getTimeOutMillis(params.getTimeoutSec()); + if (timeoutMillis > 0) { + logger.info("{}: set timeout to {}ms for {}", getFullName(), timeoutMillis, params.getRequestId()); + future = future.orTimeout(timeoutMillis, TimeUnit.MILLISECONDS); + } + + /* + * Note: we re-invoke callbackStarted() just to be sure the callback is invoked + * before callbackCompleted() is invoked. + * + * Note: no need to remove "callbacks" from the pipeline, as we're going to stop + * the pipeline as the last step anyway. + */ + + // @formatter:off + future.exceptionally(fromException(params, "operation")) + .thenApply(setRetryFlag(params, attempt)) + .whenCompleteAsync(callbackStarted(params, callbacks), executor) + .whenCompleteAsync(callbackCompleted(params, callbacks), executor) + .whenCompleteAsync(controller.delayedComplete(), executor); + // @formatter:on + + return controller; + } + + /** + * Determines if the outcome was successful. + * + * @param outcome outcome to examine + * @return {@code true} if the outcome was successful + */ + protected boolean isSuccess(OperationOutcome outcome) { + return (outcome.getResult() == PolicyResult.SUCCESS); + } + + /** + * Determines if the outcome was a failure for this operator. + * + * @param outcome outcome to examine, or {@code null} + * @return {@code true} if the outcome is not {@code null} and was a failure + * <i>and</i> was associated with this operator, {@code false} otherwise + */ + protected boolean isActorFailed(OperationOutcome outcome) { + return (isSameOperation(outcome) && outcome.getResult() == PolicyResult.FAILURE); + } + + /** + * Determines if the given outcome is for this operation. + * + * @param outcome outcome to examine + * @return {@code true} if the outcome is for this operation, {@code false} otherwise + */ + protected boolean isSameOperation(OperationOutcome outcome) { + return OperationOutcome.isFor(outcome, getActorName(), getName()); + } + + /** + * Invokes the operation as a "future". This method simply invokes + * {@link #doOperation(ControlLoopOperationParams)} using the {@link #blockingExecutor + * "blocking executor"}, returning the result via a "future". + * <p/> + * Note: if the operation uses blocking I/O, then it should <i>not</i> be run using + * the executor in the "params", as that may bring the background thread pool to a + * grinding halt. The {@link #blockingExecutor "blocking executor"} should be used + * instead. + * <p/> + * This method assumes the following: + * <ul> + * <li>the operator is alive</li> + * <li>verifyRunning() has been invoked</li> + * <li>callbackStarted() has been invoked</li> + * <li>the invoker will perform appropriate timeout checks</li> + * <li>exceptions generated within the pipeline will be handled by the invoker</li> + * </ul> + * + * @param params operation parameters + * @param attempt attempt number, typically starting with 1 + * @return a function that will start the operation and return its result when + * complete + */ + protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, int attempt, + OperationOutcome outcome) { + + return CompletableFuture.supplyAsync(() -> doOperation(params, attempt, outcome), getBlockingExecutor()); + } + + /** + * Low-level method that performs the operation. This can make the same assumptions + * that are made by {@link #doOperationAsFuture(ControlLoopOperationParams)}. This + * particular method simply throws an {@link UnsupportedOperationException}. + * + * @param params operation parameters + * @param attempt attempt number, typically starting with 1 + * @param operation the operation being performed + * @return the outcome of the operation + */ + protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt, OperationOutcome operation) { + + throw new UnsupportedOperationException("start operation " + getFullName()); + } + + /** + * Sets the outcome status to FAILURE_RETRIES, if the current operation outcome is + * FAILURE, assuming the policy specifies retries and the retry count has been + * exhausted. + * + * @param params operation parameters + * @param attempt latest attempt number, starting with 1 + * @return a function to get the next future to execute + */ + private Function<OperationOutcome, OperationOutcome> setRetryFlag(ControlLoopOperationParams params, int attempt) { + + return operation -> { + if (operation != null && !isActorFailed(operation)) { + /* + * wrong type or wrong operation - just leave it as is. No need to log + * anything here, as retryOnFailure() will log a message + */ + return operation; + } + + // get a non-null operation + OperationOutcome oper2; + if (operation != null) { + oper2 = operation; + } else { + oper2 = params.makeOutcome(); + oper2.setResult(PolicyResult.FAILURE); + } + + Integer retry = params.getRetry(); + if (retry != null && retry > 0 && attempt > retry) { + /* + * retries were specified and we've already tried them all - change to + * FAILURE_RETRIES + */ + logger.info("operation {} retries exhausted for {}", getFullName(), params.getRequestId()); + oper2.setResult(PolicyResult.FAILURE_RETRIES); + } + + return oper2; + }; + } + + /** + * Restarts the operation if it was a FAILURE. Assumes that + * {@link #setRetryFlag(ControlLoopOperationParams, int)} was previously invoked, and + * thus that the "operation" is not {@code null}. + * + * @param params operation parameters + * @param controller controller for all of the retries + * @param attempt latest attempt number, starting with 1 + * @return a function to get the next future to execute + */ + private Function<OperationOutcome, CompletableFuture<OperationOutcome>> retryOnFailure( + ControlLoopOperationParams params, PipelineControllerFuture<OperationOutcome> controller, + int attempt) { + + return operation -> { + if (!isActorFailed(operation)) { + // wrong type or wrong operation - just leave it as is + logger.trace("not retrying operation {} for {}", getFullName(), params.getRequestId()); + controller.complete(operation); + return new CompletableFuture<>(); + } + + Integer retry = params.getRetry(); + if (retry == null || retry <= 0) { + // no retries - already marked as FAILURE, so just return it + logger.info("operation {} no retries for {}", getFullName(), params.getRequestId()); + controller.complete(operation); + return new CompletableFuture<>(); + } + + + /* + * Retry the operation. + */ + logger.info("retry operation {} for {}", getFullName(), params.getRequestId()); + + return startOperationAttempt(params, controller, attempt + 1); + }; + } + + /** + * Converts an exception into an operation outcome, returning a copy of the outcome to + * prevent background jobs from changing it. + * + * @param params operation parameters + * @param type type of item throwing the exception + * @return a function that will convert an exception into an operation outcome + */ + private Function<Throwable, OperationOutcome> fromException(ControlLoopOperationParams params, String type) { + + return thrown -> { + OperationOutcome outcome = params.makeOutcome(); + + logger.warn("exception throw by {} {}.{} for {}", type, outcome.getActor(), outcome.getOperation(), + params.getRequestId(), thrown); + + return setOutcome(params, outcome, thrown); + }; + } + + /** + * Similar to {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels + * any outstanding futures when one completes. + * + * @param params operation parameters + * @param futures futures for which to wait + * @return a future to cancel or await an outcome. If this future is canceled, then + * all of the futures will be canceled + */ + protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params, + List<CompletableFuture<OperationOutcome>> futures) { + + // convert list to an array + @SuppressWarnings("rawtypes") + CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]); + + @SuppressWarnings("unchecked") + CompletableFuture<OperationOutcome> result = anyOf(params, arrFutures); + return result; + } + + /** + * Same as {@link CompletableFuture#anyOf(CompletableFuture...)}, but it cancels any + * outstanding futures when one completes. + * + * @param params operation parameters + * @param futures futures for which to wait + * @return a future to cancel or await an outcome. If this future is canceled, then + * all of the futures will be canceled + */ + protected CompletableFuture<OperationOutcome> anyOf(ControlLoopOperationParams params, + @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) { + + final Executor executor = params.getExecutor(); + final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + attachFutures(controller, futures); + + // @formatter:off + CompletableFuture.anyOf(futures) + .thenApply(object -> (OperationOutcome) object) + .whenCompleteAsync(controller.delayedComplete(), executor); + // @formatter:on + + return controller; + } + + /** + * Similar to {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels + * the futures if returned future is canceled. The future returns the "worst" outcome, + * based on priority (see {@link #detmPriority(OperationOutcome)}). + * + * @param params operation parameters + * @param futures futures for which to wait + * @return a future to cancel or await an outcome. If this future is canceled, then + * all of the futures will be canceled + */ + protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params, + List<CompletableFuture<OperationOutcome>> futures) { + + // convert list to an array + @SuppressWarnings("rawtypes") + CompletableFuture[] arrFutures = futures.toArray(new CompletableFuture[futures.size()]); + + @SuppressWarnings("unchecked") + CompletableFuture<OperationOutcome> result = allOf(params, arrFutures); + return result; + } + + /** + * Same as {@link CompletableFuture#allOf(CompletableFuture...)}, but it cancels the + * futures if returned future is canceled. The future returns the "worst" outcome, + * based on priority (see {@link #detmPriority(OperationOutcome)}). + * + * @param params operation parameters + * @param futures futures for which to wait + * @return a future to cancel or await an outcome. If this future is canceled, then + * all of the futures will be canceled + */ + protected CompletableFuture<OperationOutcome> allOf(ControlLoopOperationParams params, + @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) { + + final PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + attachFutures(controller, futures); + + OperationOutcome[] outcomes = new OperationOutcome[futures.length]; + + @SuppressWarnings("rawtypes") + CompletableFuture[] futures2 = new CompletableFuture[futures.length]; + + // record the outcomes of each future when it completes + for (int count = 0; count < futures2.length; ++count) { + final int count2 = count; + futures2[count] = futures[count].whenComplete((outcome2, thrown) -> outcomes[count2] = outcome2); + } + + CompletableFuture.allOf(futures2).whenComplete(combineOutcomes(params, controller, outcomes)); + + return controller; + } + + /** + * Attaches the given futures to the controller. + * + * @param controller master controller for all of the futures + * @param futures futures to be attached to the controller + */ + private void attachFutures(PipelineControllerFuture<OperationOutcome> controller, + @SuppressWarnings("unchecked") CompletableFuture<OperationOutcome>... futures) { + + // attach each task + for (CompletableFuture<OperationOutcome> future : futures) { + controller.add(future); + } + } + + /** + * Combines the outcomes from a set of tasks. + * + * @param params operation parameters + * @param future future to be completed with the combined result + * @param outcomes outcomes to be examined + */ + private BiConsumer<Void, Throwable> combineOutcomes(ControlLoopOperationParams params, + CompletableFuture<OperationOutcome> future, OperationOutcome[] outcomes) { + + return (unused, thrown) -> { + if (thrown != null) { + future.completeExceptionally(thrown); + return; + } + + // identify the outcome with the highest priority + OperationOutcome outcome = outcomes[0]; + int priority = detmPriority(outcome); + + // start with "1", as we've already dealt with "0" + for (int count = 1; count < outcomes.length; ++count) { + OperationOutcome outcome2 = outcomes[count]; + int priority2 = detmPriority(outcome2); + + if (priority2 > priority) { + outcome = outcome2; + priority = priority2; + } + } + + logger.trace("{}: combined outcome of tasks is {} for {}", getFullName(), + (outcome == null ? null : outcome.getResult()), params.getRequestId()); + + future.complete(outcome); + }; + } + + /** + * Determines the priority of an outcome based on its result. + * + * @param outcome outcome to examine, or {@code null} + * @return the outcome's priority + */ + protected int detmPriority(OperationOutcome outcome) { + if (outcome == null) { + return 1; + } + + switch (outcome.getResult()) { + case SUCCESS: + return 0; + + case FAILURE_GUARD: + return 2; + + case FAILURE_RETRIES: + return 3; + + case FAILURE: + return 4; + + case FAILURE_TIMEOUT: + return 5; + + case FAILURE_EXCEPTION: + default: + return 6; + } + } + + /** + * Performs a task, after verifying that the controller is still running. Also checks + * that the previous outcome was successful, if specified. + * + * @param params operation parameters + * @param controller overall pipeline controller + * @param checkSuccess {@code true} to check the previous outcome, {@code false} + * otherwise + * @param outcome outcome of the previous task + * @param tasks tasks to be performed + * @return a function to perform the task. If everything checks out, then it returns + * the task's future. Otherwise, it returns an incomplete future and completes + * the controller instead. + */ + // @formatter:off + protected CompletableFuture<OperationOutcome> doTask(ControlLoopOperationParams params, + PipelineControllerFuture<OperationOutcome> controller, + boolean checkSuccess, OperationOutcome outcome, + CompletableFuture<OperationOutcome> task) { + // @formatter:on + + if (checkSuccess && !isSuccess(outcome)) { + /* + * must complete before canceling so that cancel() doesn't cause controller to + * complete + */ + controller.complete(outcome); + task.cancel(false); + return new CompletableFuture<>(); + } + + return controller.wrap(task); + } + + /** + * Performs a task, after verifying that the controller is still running. Also checks + * that the previous outcome was successful, if specified. + * + * @param params operation parameters + * @param controller overall pipeline controller + * @param checkSuccess {@code true} to check the previous outcome, {@code false} + * otherwise + * @param tasks tasks to be performed + * @return a function to perform the task. If everything checks out, then it returns + * the task's future. Otherwise, it returns an incomplete future and completes + * the controller instead. + */ + // @formatter:off + protected Function<OperationOutcome, CompletableFuture<OperationOutcome>> doTask(ControlLoopOperationParams params, + PipelineControllerFuture<OperationOutcome> controller, + boolean checkSuccess, + Function<OperationOutcome, CompletableFuture<OperationOutcome>> task) { + // @formatter:on + + return outcome -> { + + if (!controller.isRunning()) { + return new CompletableFuture<>(); + } + + if (checkSuccess && !isSuccess(outcome)) { + controller.complete(outcome); + return new CompletableFuture<>(); + } + + return controller.wrap(task.apply(outcome)); + }; + } + + /** + * Sets the start time of the operation and invokes the callback to indicate that the + * operation has started. Does nothing if the pipeline has been stopped. + * <p/> + * This assumes that the "outcome" is not {@code null}. + * + * @param params operation parameters + * @param callbacks used to determine if the start callback can be invoked + * @return a function that sets the start time and invokes the callback + */ + private BiConsumer<OperationOutcome, Throwable> callbackStarted(ControlLoopOperationParams params, + CallbackManager callbacks) { + + return (outcome, thrown) -> { + + if (callbacks.canStart()) { + // haven't invoked "start" callback yet + outcome.setStart(callbacks.getStartTime()); + outcome.setEnd(null); + params.callbackStarted(outcome); + } + }; + } + + /** + * Sets the end time of the operation and invokes the callback to indicate that the + * operation has completed. Does nothing if the pipeline has been stopped. + * <p/> + * This assumes that the "outcome" is not {@code null}. + * <p/> + * Note: the start time must be a reference rather than a plain value, because it's + * value must be gotten on-demand, when the returned function is executed at a later + * time. + * + * @param params operation parameters + * @param callbacks used to determine if the end callback can be invoked + * @return a function that sets the end time and invokes the callback + */ + private BiConsumer<OperationOutcome, Throwable> callbackCompleted(ControlLoopOperationParams params, + CallbackManager callbacks) { + + return (outcome, thrown) -> { + + if (callbacks.canEnd()) { + outcome.setStart(callbacks.getStartTime()); + outcome.setEnd(callbacks.getEndTime()); + params.callbackCompleted(outcome); + } + }; + } + + /** + * Sets an operation's outcome and message, based on a throwable. + * + * @param params operation parameters + * @param operation operation to be updated + * @return the updated operation + */ + protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation, + Throwable thrown) { + PolicyResult result = (isTimeout(thrown) ? PolicyResult.FAILURE_TIMEOUT : PolicyResult.FAILURE_EXCEPTION); + return setOutcome(params, operation, result); + } + + /** + * Sets an operation's outcome and default message based on the result. + * + * @param params operation parameters + * @param operation operation to be updated + * @param result result of the operation + * @return the updated operation + */ + protected OperationOutcome setOutcome(ControlLoopOperationParams params, OperationOutcome operation, + PolicyResult result) { + logger.trace("{}: set outcome {} for {}", getFullName(), result, params.getRequestId()); + operation.setResult(result); + operation.setMessage(result == PolicyResult.SUCCESS ? ControlLoopOperation.SUCCESS_MSG + : ControlLoopOperation.FAILED_MSG); + + return operation; + } + + /** + * Determines if a throwable is due to a timeout. + * + * @param thrown throwable of interest + * @return {@code true} if the throwable is due to a timeout, {@code false} otherwise + */ + protected boolean isTimeout(Throwable thrown) { + if (thrown instanceof CompletionException) { + thrown = thrown.getCause(); + } + + return (thrown instanceof TimeoutException); + } + + // these may be overridden by junit tests + + /** + * Gets the operation timeout. Subclasses may override this method to obtain the + * timeout in some other way (e.g., through configuration properties). + * + * @param timeoutSec timeout, in seconds, or {@code null} + * @return the operation timeout, in milliseconds + */ + protected long getTimeOutMillis(Integer timeoutSec) { + return (timeoutSec == null ? 0 : TimeUnit.MILLISECONDS.convert(timeoutSec, TimeUnit.SECONDS)); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartial.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartial.java new file mode 100644 index 000000000..6c883f1b5 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartial.java @@ -0,0 +1,153 @@ +/*- + * ============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 lombok.Getter; +import org.onap.policy.common.capabilities.Configurable; +import org.onap.policy.common.capabilities.Startable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Partial implementation of an object that is both startable and configurable. It + * provides the high level methods defined in the interface, while deferring the details + * to abstract methods that must be provided by the subclasses. It also manages the + * current {@link #state}. + * + * @param <T> type of parameters expected by {@link #configure(Object)} + */ +public abstract class StartConfigPartial<T> implements Startable, Configurable<T> { + private static final Logger logger = LoggerFactory.getLogger(StartConfigPartial.class); + + @Getter + private final String fullName; + + public enum State { + IDLE, CONFIGURED, ALIVE + } + + private State state = State.IDLE; + + /** + * Constructs the object. + * + * @param fullName full name of this object, used for logging and exception purposes + */ + public StartConfigPartial(String fullName) { + this.fullName = fullName; + } + + @Override + public synchronized boolean isAlive() { + return (state == State.ALIVE); + } + + /** + * Determines if this object has been configured. + * + * @return {@code true} if this object has been configured, {@code false} otherwise + */ + public synchronized boolean isConfigured() { + return (state != State.IDLE); + } + + @Override + public synchronized void configure(T parameters) { + if (isAlive()) { + throw new IllegalStateException("attempt to reconfigure, but already running " + getFullName()); + } + + logger.info("initializing {}", getFullName()); + + doConfigure(parameters); + + state = State.CONFIGURED; + } + + @Override + public synchronized boolean start() { + switch (state) { + case ALIVE: + logger.info("{} is already running", getFullName()); + break; + + case CONFIGURED: + logger.info("starting {}", getFullName()); + doStart(); + state = State.ALIVE; + break; + + case IDLE: + default: + throw new IllegalStateException("attempt to start unconfigured " + getFullName()); + } + + return true; + } + + @Override + public synchronized boolean stop() { + if (isAlive()) { + logger.info("stopping {}", getFullName()); + state = State.CONFIGURED; + doStop(); + + } else { + logger.info("{} is not running", getFullName()); + } + + return true; + } + + @Override + public synchronized void shutdown() { + if (!isAlive()) { + logger.info("{} is not running", getFullName()); + return; + } + + logger.info("shutting down actor {}", getFullName()); + state = State.CONFIGURED; + doShutdown(); + } + + /** + * Configures this object. + * + * @param parameters configuration parameters + */ + protected abstract void doConfigure(T parameters); + + /** + * Starts this object. + */ + protected abstract void doStart(); + + /** + * Stops this object. + */ + protected abstract void doStop(); + + /** + * Shuts down this object. + */ + protected abstract void doShutdown(); +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java new file mode 100644 index 000000000..57fce40d7 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParams.java @@ -0,0 +1,218 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.function.Consumer; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.onap.policy.common.parameters.BeanValidationResult; +import org.onap.policy.common.parameters.BeanValidator; +import org.onap.policy.common.parameters.annotations.NotNull; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.policy.Target; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Parameters for control loop operations. The executor defaults to + * {@link ForkJoinPool#commonPool()}, but may be overridden. + */ +@Getter +@Builder(toBuilder = true) +@AllArgsConstructor +@EqualsAndHashCode +public class ControlLoopOperationParams { + private static final Logger logger = LoggerFactory.getLogger(ControlLoopOperationParams.class); + + /** + * Actor name. + */ + @NotNull + private String actor; + + /** + * Actor service in which to find the actor/operation. + */ + @NotNull + private ActorService actorService; + + /** + * Event for which the operation applies. + */ + @NotNull + private ControlLoopEventContext context; + + /** + * Executor to use to run the operation. + */ + @NotNull + @Builder.Default + private Executor executor = ForkJoinPool.commonPool(); + + /** + * Operation name. + */ + @NotNull + private String operation; + + /** + * Payload data for the request. + */ + private Map<String, String> payload; + + /** + * Number of retries allowed, or {@code null} if no retries. + */ + private Integer retry; + + /** + * The entity's target information. May be {@code null}, depending on the requirement + * of the operation to be invoked. + */ + private Target target; + + /** + * Target entity. + */ + @NotNull + private String targetEntity; + + /** + * Timeout, in seconds, or {@code null} if no timeout. Zero and negative values also + * imply no timeout. + */ + @Builder.Default + private Integer timeoutSec = 300; + + /** + * The function to invoke when the operation starts. This is optional. + * <p/> + * Note: this may be invoked multiple times, but with different actor/operations. That + * may happen if the current operation requires other operations to be performed first + * (e.g., A&AI queries, guard checks). + */ + private Consumer<OperationOutcome> startCallback; + + /** + * The function to invoke when the operation completes. This is optional. + * <p/> + * Note: this may be invoked multiple times, but with different actor/operations. That + * may happen if the current operation requires other operations to be performed first + * (e.g., A&AI queries, guard checks). + */ + private Consumer<OperationOutcome> completeCallback; + + /** + * Starts the specified operation. + * + * @return a future that will return the result of the operation + * @throws IllegalArgumentException if the parameters are invalid + */ + public CompletableFuture<OperationOutcome> start() { + BeanValidationResult result = validate(); + if (!result.isValid()) { + logger.warn("parameter error in operation {}.{} for {}:\n{}", getActor(), getOperation(), getRequestId(), + result.getResult()); + throw new IllegalArgumentException("invalid parameters"); + } + + // @formatter:off + return actorService + .getActor(getActor()) + .getOperator(getOperation()) + .startOperation(this); + // @formatter:on + } + + /** + * Gets the requested ID of the associated event. + * + * @return the event's request ID, or {@code null} if no request ID is available + */ + public UUID getRequestId() { + return (context == null || context.getEvent() == null ? null : context.getEvent().getRequestId()); + } + + /** + * Makes an operation outcome, populating it from the parameters. + * + * @return a new operation outcome + */ + public OperationOutcome makeOutcome() { + OperationOutcome outcome = new OperationOutcome(); + outcome.setActor(getActor()); + outcome.setOperation(getOperation()); + outcome.setTarget(targetEntity); + + return outcome; + } + + /** + * Invokes the callback to indicate that the operation has started. Any exceptions + * generated by the callback are logged, but not re-thrown. + * + * @param operation the operation that is being started + */ + public void callbackStarted(OperationOutcome operation) { + logger.info("started operation {}.{} for {}", operation.getActor(), operation.getOperation(), getRequestId()); + + if (startCallback != null) { + Util.runFunction(() -> startCallback.accept(operation), "{}.{}: start-callback threw an exception for {}", + operation.getActor(), operation.getOperation(), getRequestId()); + } + } + + /** + * Invokes the callback to indicate that the operation has completed. Any exceptions + * generated by the callback are logged, but not re-thrown. + * + * @param operation the operation that is being started + */ + public void callbackCompleted(OperationOutcome operation) { + logger.info("completed operation {}.{} outcome={} for {}", operation.getActor(), operation.getOperation(), + operation.getResult(), getRequestId()); + + if (completeCallback != null) { + Util.runFunction(() -> completeCallback.accept(operation), + "{}.{}: complete-callback threw an exception for {}", operation.getActor(), + operation.getOperation(), getRequestId()); + } + } + + /** + * Validates the parameters. + * + * @return the validation result + */ + public BeanValidationResult validate() { + return new BeanValidator().validateTop(ControlLoopOperationParams.class.getSimpleName(), this); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java new file mode 100644 index 000000000..da4fb4f0c --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParams.java @@ -0,0 +1,112 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import java.util.Map; +import java.util.function.Function; +import lombok.Data; +import org.onap.policy.common.parameters.BeanValidationResult; +import org.onap.policy.common.parameters.BeanValidator; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.parameters.annotations.Min; +import org.onap.policy.common.parameters.annotations.NotBlank; +import org.onap.policy.common.parameters.annotations.NotNull; +import org.onap.policy.controlloop.actorserviceprovider.Util; + +/** + * Parameters used by Actors that connect to a server via HTTP. This contains the + * parameters that are common to all of the operations. Only the path changes for each + * operation, thus it includes a mapping from operation name to path. + */ +@Data +@NotNull +@NotBlank +public class HttpActorParams { + + /** + * Name of the HttpClient, as found in the HttpClientFactory. + */ + private String clientName; + + /** + * Amount of time, in seconds to wait for the HTTP request to complete, where zero + * indicates that it should wait forever. The default is zero. + */ + @Min(0) + private long timeoutSec = 0; + + /** + * Maps the operation name to its URI path. + */ + private Map<String, String> path; + + /** + * Extracts a specific operation's parameters from "this". + * + * @param name name of the item containing "this" + * @return a function to extract an operation's parameters from "this". Note: the + * returned function is not thread-safe + */ + public Function<String, Map<String, Object>> makeOperationParameters(String name) { + HttpParams subparams = HttpParams.builder().clientName(getClientName()).timeoutSec(getTimeoutSec()).build(); + + return operation -> { + String subpath = path.get(operation); + if (subpath == null) { + return null; + } + + subparams.setPath(subpath); + return Util.translateToMap(name + "." + operation, subparams); + }; + } + + /** + * Validates the parameters. + * + * @param name name of the object containing these parameters + * @return "this" + * @throws IllegalArgumentException if the parameters are invalid + */ + public HttpActorParams doValidation(String name) { + ValidationResult result = validate(name); + if (!result.isValid()) { + throw new ParameterValidationRuntimeException("invalid parameters", result); + } + + return this; + } + + /** + * Validates the parameters. + * + * @param resultName name of the result + * + * @return the validation result + */ + public ValidationResult validate(String resultName) { + BeanValidationResult result = new BeanValidator().validateTop(resultName, this); + + result.validateMap("path", path, (result2, entry) -> result2.validateNotNull(entry.getKey(), entry.getValue())); + + return result; + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java new file mode 100644 index 000000000..695ffe4dd --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParams.java @@ -0,0 +1,69 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import lombok.Builder; +import lombok.Data; +import org.onap.policy.common.parameters.BeanValidator; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.parameters.annotations.Min; +import org.onap.policy.common.parameters.annotations.NotBlank; +import org.onap.policy.common.parameters.annotations.NotNull; + +/** + * Parameters used by Operators that connect to a server via HTTP. + */ +@NotNull +@NotBlank +@Data +@Builder(toBuilder = true) +public class HttpParams { + + /** + * Name of the HttpClient, as found in the HttpClientFactory. + */ + private String clientName; + + /** + * URI path. + */ + private String path; + + /** + * Amount of time, in seconds to wait for the HTTP request to complete, where zero + * indicates that it should wait forever. The default is zero. + */ + @Min(0) + @Builder.Default + private long timeoutSec = 0; + + + /** + * Validates the parameters. + * + * @param resultName name of the result + * + * @return the validation result + */ + public ValidationResult validate(String resultName) { + return new BeanValidator().validateTop(resultName, this); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeException.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeException.java new file mode 100644 index 000000000..3004e1932 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeException.java @@ -0,0 +1,57 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import lombok.Getter; +import org.onap.policy.common.parameters.ValidationResult; + +/** + * Parameter runtime exception, with an associated validation result. This is used to + * throw an exception while passing a validation result up the chain. + * <p/> + * Note: the validation result is <i>not</i> included in the exception message. + */ +public class ParameterValidationRuntimeException extends RuntimeException { + private static final long serialVersionUID = 1L; + + @Getter + private final transient ValidationResult result; + + + public ParameterValidationRuntimeException(ValidationResult result) { + this.result = result; + } + + public ParameterValidationRuntimeException(String message, ValidationResult result) { + super(message); + this.result = result; + } + + public ParameterValidationRuntimeException(Throwable cause, ValidationResult result) { + super(cause); + this.result = result; + } + + public ParameterValidationRuntimeException(String message, Throwable cause, ValidationResult result) { + super(message, cause); + this.result = result; + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java new file mode 100644 index 000000000..9e6d8a15e --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParams.java @@ -0,0 +1,68 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import lombok.Builder; +import lombok.Data; +import org.onap.policy.common.parameters.BeanValidator; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.parameters.annotations.Min; +import org.onap.policy.common.parameters.annotations.NotBlank; +import org.onap.policy.common.parameters.annotations.NotNull; + +/** + * Parameters used by Operators that connect to a server via DMaaP. + */ +@NotNull +@NotBlank +@Data +@Builder(toBuilder = true) +public class TopicParams { + + /** + * Name of the target topic end point to which requests should be published. + */ + private String target; + + /** + * Source topic end point, from which to read responses. + */ + private String source; + + /** + * Amount of time, in seconds to wait for the response, where zero indicates that it + * should wait forever. The default is zero. + */ + @Min(0) + @Builder.Default + private long timeoutSec = 0; + + /** + * Validates both the publisher and the subscriber parameters. + * + * @param resultName name of the result + * + * @return the validation result + */ + public ValidationResult validate(String resultName) { + return new BeanValidator().validateTop(resultName, this); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManager.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManager.java new file mode 100644 index 000000000..aac2f77b7 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManager.java @@ -0,0 +1,89 @@ +/*- + * ============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.pipeline; + +import java.util.IdentityHashMap; +import java.util.concurrent.Future; +import lombok.NoArgsConstructor; + +/** + * Manager that manages both futures and listeners. When {@link #stop()} is called, the + * listeners are executed and the futures are canceled. The various methods synchronize on + * "this" while they manipulate internal data structures. + */ +@NoArgsConstructor +public class FutureManager extends ListenerManager { + + /** + * Maps a future to its listener. Records the {@link Runnable} that is passed to + * {@link ListenerManager#add(Runnable)} when {@link #add(Future)} is invoked. This is + * needed if {@link #remove(Future)} is invoked, so that the same {@link Runnable} is + * used each time. + */ + @SuppressWarnings("rawtypes") + private final IdentityHashMap<Future, Runnable> future2listener = new IdentityHashMap<>(5); + + /** + * Adds a future that is to be canceled when this controller is stopped. Note: if the + * controller is already stopped, then the future will be canceled immediately, within + * the invoking thread. + * + * @param future future to be added + */ + public <T> void add(Future<T> future) { + Runnable listener = () -> future.cancel(false); + + synchronized (this) { + if (future2listener.putIfAbsent(future, listener) != null) { + // this future is already in the map, nothing more to do + return; + } + + if (addOnly(listener)) { + // successfully added + return; + } + } + + runListener(listener); + } + + /** + * Removes a future so that it is not canceled when this controller is stopped. + * + * @param future future to be removed + */ + public synchronized <T> void remove(Future<T> future) { + Runnable listener = future2listener.remove(future); + if (listener != null) { + remove(listener); + } + } + + @Override + public void stop() { + super.stop(); + + synchronized (this) { + future2listener.clear(); + } + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManager.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManager.java new file mode 100644 index 000000000..1d64a8710 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManager.java @@ -0,0 +1,115 @@ +/*- + * ============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.pipeline; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.onap.policy.controlloop.actorserviceprovider.Util; + +/** + * Listener manager, used by operations within the pipeline to determine if they should + * continue to run. When {@link #stop()} is called, the listeners are executed. The + * various methods synchronize on "this" while they manipulate internal data structures. + */ +@NoArgsConstructor +public class ListenerManager { + + @Getter + private volatile boolean running = true; + + /** + * Listeners to be executed when {@link #stop()} is invoked. + */ + private final IdentityHashMap<Runnable, Void> listeners = new IdentityHashMap<>(5); + + /** + * Indicates that operations within the pipeline should stop executing. + */ + public void stop() { + ArrayList<Runnable> items; + + synchronized (this) { + if (!running) { + return; + } + + running = false; + items = new ArrayList<>(listeners.keySet()); + listeners.clear(); + } + + items.forEach(this::runListener); + } + + /** + * Adds a listener that is to be invoked when this controller is stopped. Note: if the + * controller is already stopped, then the listener will be invoked immediately, + * within the invoking thread. + * + * @param listener listener to be added + */ + public void add(Runnable listener) { + if (!addOnly(listener)) { + runListener(listener); + } + } + + /** + * Adds a listener that is to be invoked when this controller is stopped. Note: if the + * controller is already stopped, then the listener will be invoked immediately, + * within the invoking thread. + * + * @param listener listener to be added + * @return {@code true} if the the listener was added, {@code false} if it could not + * be added because this manager has already been stopped + */ + protected boolean addOnly(Runnable listener) { + synchronized (this) { + if (running) { + listeners.put(listener, null); + return true; + } + } + + return false; + } + + /** + * Runs a listener, catching any exceptions that it may throw. + * + * @param listener listener to be executed + */ + protected void runListener(Runnable listener) { + // TODO do this asynchronously? + Util.runFunction(listener, "pipeline listener {} threw an exception", listener); + } + + /** + * Removes a listener so that it is not invoked when this controller is stopped. + * + * @param listener listener to be removed + */ + public synchronized void remove(Runnable listener) { + listeners.remove(listener); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFuture.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFuture.java new file mode 100644 index 000000000..92843e28a --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFuture.java @@ -0,0 +1,220 @@ +/*- + * ============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.pipeline; + +import static org.onap.policy.controlloop.actorserviceprovider.Util.ident; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; +import lombok.NoArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pipeline controller, used by operations within the pipeline to determine if they should + * continue to run. Whenever this is canceled or completed, it automatically cancels all + * futures and runs all listeners that have been added. + */ +@NoArgsConstructor +public class PipelineControllerFuture<T> extends CompletableFuture<T> { + + private static final Logger logger = LoggerFactory.getLogger(PipelineControllerFuture.class); + + private static final String COMPLETE_EXCEPT_MSG = "{}: complete future with exception"; + private static final String CANCEL_MSG = "{}: cancel future"; + private static final String COMPLETE_MSG = "{}: complete future"; + + /** + * Tracks items added to this controller via one of the <i>add</i> methods. + */ + private final FutureManager futures = new FutureManager(); + + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return doAndStop(() -> super.cancel(mayInterruptIfRunning), CANCEL_MSG, ident(this)); + } + + @Override + public boolean complete(T value) { + return doAndStop(() -> super.complete(value), COMPLETE_MSG, ident(this)); + } + + @Override + public boolean completeExceptionally(Throwable ex) { + return doAndStop(() -> super.completeExceptionally(ex), COMPLETE_EXCEPT_MSG, ident(this)); + } + + @Override + public CompletableFuture<T> completeAsync(Supplier<? extends T> supplier, Executor executor) { + return super.completeAsync(() -> doAndStop(supplier, COMPLETE_MSG, ident(this)), executor); + } + + @Override + public CompletableFuture<T> completeAsync(Supplier<? extends T> supplier) { + return super.completeAsync(() -> doAndStop(supplier, COMPLETE_MSG, ident(this))); + } + + @Override + public CompletableFuture<T> completeOnTimeout(T value, long timeout, TimeUnit unit) { + logger.info("{}: set future timeout to {} {}", ident(this), timeout, unit); + return super.completeOnTimeout(value, timeout, unit); + } + + @Override + public <U> PipelineControllerFuture<U> newIncompleteFuture() { + return new PipelineControllerFuture<>(); + } + + /** + * Generates a function that, when invoked, will remove the given future. This is + * typically added onto the end of a pipeline via one of the + * {@link CompletableFuture#whenComplete(BiConsumer)} methods. + * + * @return a function that removes the given future + */ + public <F> BiConsumer<F, Throwable> delayedRemove(Future<F> future) { + return (value, thrown) -> remove(future); + } + + /** + * Generates a function that, when invoked, will remove the given listener. This is + * typically added onto the end of a pipeline via one of the + * {@link CompletableFuture#whenComplete(BiConsumer)} methods. + * + * @return a function that removes the given listener + */ + public <F> BiConsumer<F, Throwable> delayedRemove(Runnable listener) { + return (value, thrown) -> remove(listener); + } + + /** + * Generates a function that, when invoked, will stop all pipeline listeners and + * complete this future. This is typically added onto the end of a pipeline via one of + * the {@link CompletableFuture#whenComplete(BiConsumer)} methods. + * + * @return a function that stops all pipeline listeners + */ + public BiConsumer<T, Throwable> delayedComplete() { + return (value, thrown) -> { + if (thrown == null) { + complete(value); + } else { + completeExceptionally(thrown); + } + }; + } + + /** + * Adds a future to the controller and arranges for it to be removed from the + * controller when it completes, whether or not it throws an exception. If the + * controller has already been stopped, then the future is canceled and a new, + * incomplete future is returned. + * + * @param future future to be wrapped + * @return a new future + */ + public CompletableFuture<T> wrap(CompletableFuture<T> future) { + if (!isRunning()) { + logger.trace("{}: not running, skipping next task {}", ident(this), ident(future)); + future.cancel(false); + return new CompletableFuture<>(); + } + + add(future); + return future.whenComplete(this.delayedRemove(future)); + } + + /** + * Adds a function whose return value is to be canceled when this controller is + * stopped. Note: if the controller is already stopped, then the function will + * <i>not</i> be executed. + * + * @param futureMaker function to be invoked to create the future + * @return a function to create the future and arrange for it to be managed by this + * controller + */ + public <F> Function<F, CompletableFuture<F>> wrap(Function<F, CompletableFuture<F>> futureMaker) { + + return input -> { + if (!isRunning()) { + logger.trace("{}: discarded new future", ident(this)); + return new CompletableFuture<>(); + } + + CompletableFuture<F> future = futureMaker.apply(input); + add(future); + + return future.whenComplete(delayedRemove(future)); + }; + } + + public <F> void add(Future<F> future) { + logger.trace("{}: add future {}", ident(this), ident(future)); + futures.add(future); + } + + public void add(Runnable listener) { + logger.trace("{}: add listener {}", ident(this), ident(listener)); + futures.add(listener); + } + + public boolean isRunning() { + return futures.isRunning(); + } + + public <F> void remove(Future<F> future) { + logger.trace("{}: remove future {}", ident(this), ident(future)); + futures.remove(future); + } + + public void remove(Runnable listener) { + logger.trace("{}: remove listener {}", ident(this), ident(listener)); + futures.remove(listener); + } + + /** + * Performs an operation, stops the futures, and returns the value from the operation. + * Logs a message using the given arguments. + * + * + * @param <R> type of value to be returned + * @param supplier operation to perform + * @param message message to be logged + * @param args message arguments to fill "{}" place-holders + * @return the operation's result + */ + private <R> R doAndStop(Supplier<R> supplier, String message, Object... args) { + try { + logger.trace(message, args); + return supplier.get(); + + } finally { + logger.trace("{}: stopping this future", ident(this)); + futures.stop(); + } + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java index 88f3c16eb..620950a3c 100644 --- a/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java +++ b/models-interactions/model-actors/actorServiceProvider/src/main/java/org/onap/policy/controlloop/actorserviceprovider/spi/Actor.java @@ -2,7 +2,7 @@ * ============LICENSE_START======================================================= * Actor * ================================================================================ - * Copyright (C) 2017-2018 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2017-2018, 2020 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,56 @@ package org.onap.policy.controlloop.actorserviceprovider.spi; +import java.util.Collection; + import java.util.List; +import java.util.Map; +import java.util.Set; +import org.onap.policy.common.capabilities.Configurable; +import org.onap.policy.common.capabilities.Startable; +import org.onap.policy.controlloop.actorserviceprovider.Operator; + +/** + * This is the service interface for defining an Actor used in Control Loop Operational + * Policies for performing actions on runtime entities. + * + * @author pameladragosh + * + */ +public interface Actor extends Startable, Configurable<Map<String,Object>> { + + /** + * Gets the name of the actor. + * + * @return the actor name + */ + String getName(); + + /** + * Gets a particular operator. + * + * @param name name of the operation of interest + * @return the desired operation + * @throws IllegalArgumentException if no operation by the given name exists + */ + Operator getOperator(String name); + + /** + * Gets the supported operations. + * + * @return the supported operations + */ + public Collection<Operator> getOperators(); + + /** + * Gets the names of the supported operations. + * + * @return the names of the supported operations + */ + public Set<String> getOperationNames(); + -public interface Actor { + // TODO old code: remove lines down to **HERE** String actor(); @@ -33,4 +80,5 @@ public interface Actor { List<String> recipePayloads(String recipe); + // **HERE** } diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java index 7ab21dece..139c5179b 100644 --- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java @@ -4,7 +4,7 @@ * ================================================================================ * Copyright (C) 2018 Ericsson. All rights reserved. * Modifications Copyright (C) 2019 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. @@ -28,6 +28,8 @@ import static org.junit.Assert.assertNotNull; import org.junit.Test; import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +// TODO combine this with ActorServiceTest + public class ActorServiceProviderTest { private static final String DOROTHY = "Dorothy"; @@ -37,12 +39,12 @@ public class ActorServiceProviderTest { ActorService actorService = ActorService.getInstance(); assertNotNull(actorService); - assertEquals(1, actorService.actors().size()); + assertEquals(1, actorService.getActors().size()); actorService = ActorService.getInstance(); assertNotNull(actorService); - Actor dummyActor = ActorService.getInstance().actors().get(0); + Actor dummyActor = ActorService.getInstance().getActors().iterator().next(); assertNotNull(dummyActor); assertEquals("DummyActor", dummyActor.actor()); diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java new file mode 100644 index 000000000..851a79129 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java @@ -0,0 +1,382 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +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.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ObjectValidationResult; +import org.onap.policy.common.parameters.ValidationStatus; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; +import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; + +public class ActorServiceTest { + private static final String EXPECTED_EXCEPTION = "expected exception"; + private static final String ACTOR1 = "actor A"; + private static final String ACTOR2 = "actor B"; + private static final String ACTOR3 = "actor C"; + private static final String ACTOR4 = "actor D"; + + private Actor actor1; + private Actor actor2; + private Actor actor3; + private Actor actor4; + + private Map<String, Object> sub1; + private Map<String, Object> sub2; + private Map<String, Object> sub3; + private Map<String, Object> sub4; + private Map<String, Object> params; + + private ActorService service; + + + /** + * Initializes the fields, including a fully populated {@link #service}. + */ + @Before + public void setUp() { + actor1 = spy(new ActorImpl(ACTOR1)); + actor2 = spy(new ActorImpl(ACTOR2)); + actor3 = spy(new ActorImpl(ACTOR3)); + actor4 = spy(new ActorImpl(ACTOR4)); + + sub1 = Map.of("sub A", "value A"); + sub2 = Map.of("sub B", "value B"); + sub3 = Map.of("sub C", "value C"); + sub4 = Map.of("sub D", "value D"); + + params = Map.of(ACTOR1, sub1, ACTOR2, sub2, ACTOR3, sub3, ACTOR4, sub4); + + service = makeService(actor1, actor2, actor3, actor4); + } + + @Test + public void testActorService() { + /* + * make a service where actors two and four have names that are duplicates of the + * others + */ + actor2 = spy(new ActorImpl(ACTOR1)); + actor4 = spy(new ActorImpl(ACTOR3)); + + service = makeService(actor1, actor2, actor3, actor4); + + assertEquals(2, service.getActorNames().size()); + + assertSame(actor1, service.getActor(ACTOR1)); + assertSame(actor3, service.getActor(ACTOR3)); + } + + @Test + public void testDoStart() { + service.configure(params); + + setUpOp("testDoStart", actor -> when(actor.isConfigured()).thenReturn(false), Actor::start); + + /* + * Start the service. + */ + service.start(); + assertTrue(service.isAlive()); + + Iterator<Actor> iter = service.getActors().iterator(); + verify(iter.next()).start(); + verify(iter.next(), never()).start(); + verify(iter.next()).start(); + verify(iter.next()).start(); + + // no additional types of operations + verify(actor1).configure(any()); + + // no other types of operations + verify(actor1, never()).stop(); + verify(actor1, never()).shutdown(); + } + + @Test + public void testDoStop() { + service.configure(params); + service.start(); + + setUpOp("testDoStop", Actor::stop, Actor::stop); + + /* + * Stop the service. + */ + service.stop(); + assertFalse(service.isAlive()); + + Iterator<Actor> iter = service.getActors().iterator(); + verify(iter.next()).stop(); + verify(iter.next(), times(2)).stop(); + verify(iter.next()).stop(); + verify(iter.next()).stop(); + + // no additional types of operations + verify(actor1).configure(any()); + verify(actor1).start(); + + // no other types of operation + verify(actor1, never()).shutdown(); + } + + @Test + public void testDoShutdown() { + service.configure(params); + service.start(); + + setUpOp("testDoShutdown", Actor::shutdown, Actor::shutdown); + + /* + * Shut down the service. + */ + service.shutdown(); + assertFalse(service.isAlive()); + + Iterator<Actor> iter = service.getActors().iterator(); + verify(iter.next()).shutdown(); + verify(iter.next(), times(2)).shutdown(); + verify(iter.next()).shutdown(); + verify(iter.next()).shutdown(); + + // no additional types of operations + verify(actor1).configure(any()); + verify(actor1).start(); + + // no other types of operation + verify(actor1, never()).stop(); + } + + /** + * Applies an operation to the second actor, and then arranges for the third actor to + * throw an exception when its operation is performed. + * + * @param testName test name + * @param oper2 operation to apply to the second actor + * @param oper3 operation to apply to the third actor + */ + private void setUpOp(String testName, Consumer<Actor> oper2, Consumer<Actor> oper3) { + Collection<Actor> actors = service.getActors(); + assertEquals(testName, 4, actors.size()); + + Iterator<Actor> iter = actors.iterator(); + + // leave the first alone + iter.next(); + + // apply oper2 to the second actor + oper2.accept(iter.next()); + + // throw an exception in the third + oper3.accept(doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(iter.next())); + + // leave the fourth alone + iter.next(); + } + + @Test + public void testGetInstance() { + service = ActorService.getInstance(); + assertNotNull(service); + + assertSame(service, ActorService.getInstance()); + } + + @Test + public void testGetActor() { + assertSame(actor1, service.getActor(ACTOR1)); + assertSame(actor3, service.getActor(ACTOR3)); + + assertThatIllegalArgumentException().isThrownBy(() -> service.getActor("unknown actor")); + } + + @Test + public void testGetActors() { + // @formatter:off + assertEquals("[actor A, actor B, actor C, actor D]", + service.getActors().stream() + .map(Actor::getName) + .sorted() + .collect(Collectors.toList()) + .toString()); + // @formatter:on + } + + @Test + public void testGetActorNames() { + // @formatter:off + assertEquals("[actor A, actor B, actor C, actor D]", + service.getActorNames().stream() + .sorted() + .collect(Collectors.toList()) + .toString()); + // @formatter:on + } + + @Test + public void testDoConfigure() { + service.configure(params); + assertTrue(service.isConfigured()); + + verify(actor1).configure(sub1); + verify(actor2).configure(sub2); + verify(actor3).configure(sub3); + verify(actor4).configure(sub4); + + // no other types of operations + verify(actor1, never()).start(); + verify(actor1, never()).stop(); + verify(actor1, never()).shutdown(); + } + + /** + * Tests doConfigure() where actors throw parameter validation and runtime exceptions. + */ + @Test + public void testDoConfigureExceptions() { + makeValidException(actor1); + makeRuntimeException(actor2); + makeValidException(actor3); + + service.configure(params); + assertTrue(service.isConfigured()); + } + + /** + * Tests doConfigure(). Arranges for the following: + * <ul> + * <li>one actor is configured, but has parameters</li> + * <li>another actor is configured, but has no parameters</li> + * <li>another actor has no parameters and is not configured</li> + * </ul> + */ + @Test + public void testDoConfigureConfigure() { + // need mutable parameters + params = new TreeMap<>(params); + + // configure one actor + actor1.configure(sub1); + + // configure another and remove its parameters + actor2.configure(sub2); + params.remove(ACTOR2); + + // create a new, unconfigured actor + ActorImpl actor5 = spy(new ActorImpl("UNCONFIGURED")); + service = makeService(actor1, actor2, actor3, actor4, actor5); + + /* + * Configure it. + */ + service.configure(params); + assertTrue(service.isConfigured()); + + // this should have been configured again + verify(actor1, times(2)).configure(sub1); + + // no parameters, so this should not have been configured again + verify(actor2).configure(sub2); + + // these were only configured once + verify(actor3).configure(sub3); + verify(actor4).configure(sub4); + + // never configured + verify(actor5, never()).configure(any()); + assertFalse(actor5.isConfigured()); + + // start and verify that all are started except for the last + service.start(); + verify(actor1).start(); + verify(actor2).start(); + verify(actor3).start(); + verify(actor4).start(); + verify(actor5, never()).start(); + } + + /** + * Arranges for an actor to throw a validation exception when + * {@link Actor#configure(Map)} is invoked. + * + * @param actor actor of interest + */ + private void makeValidException(Actor actor) { + ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException( + new ObjectValidationResult(actor.getName(), null, ValidationStatus.INVALID, "null")); + doThrow(ex).when(actor).configure(any()); + } + + /** + * Arranges for an actor to throw a runtime exception when + * {@link Actor#configure(Map)} is invoked. + * + * @param actor actor of interest + */ + private void makeRuntimeException(Actor actor) { + IllegalStateException ex = new IllegalStateException(EXPECTED_EXCEPTION); + doThrow(ex).when(actor).configure(any()); + } + + @Test + public void testLoadActors() { + assertFalse(ActorService.getInstance().getActors().isEmpty()); + assertNotNull(ActorService.getInstance().getActor("DummyActor")); + } + + /** + * Makes an actor service whose {@link ActorService#loadActors()} method returns the + * given actors. + * + * @param actors actors to be returned + * @return a new actor service + */ + private ActorService makeService(Actor... actors) { + return new ActorService() { + @Override + protected Iterable<Actor> loadActors() { + return Arrays.asList(actors); + } + }; + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java new file mode 100644 index 000000000..31c6d2077 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/AsyncResponseHandlerTest.java @@ -0,0 +1,172 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.policy.PolicyResult; + +public class AsyncResponseHandlerTest { + + private static final String ACTOR = "my-actor"; + private static final String OPERATION = "my-operation"; + private static final UUID REQ_ID = UUID.randomUUID(); + private static final String TEXT = "some text"; + + private VirtualControlLoopEvent event; + private ControlLoopEventContext context; + private ControlLoopOperationParams params; + private OperationOutcome outcome; + private MyHandler handler; + + /** + * Initializes all fields, including {@link #handler}. + */ + @Before + public void setUp() { + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + + context = new ControlLoopEventContext(event); + params = ControlLoopOperationParams.builder().actor(ACTOR).operation(OPERATION).context(context).build(); + outcome = params.makeOutcome(); + + handler = new MyHandler(params, outcome); + } + + @Test + public void testAsyncResponseHandler_testGetParams_testGetOutcome() { + assertSame(params, handler.getParams()); + assertSame(outcome, handler.getOutcome()); + } + + @Test + public void testHandle() { + CompletableFuture<String> future = new CompletableFuture<>(); + handler.handle(future).complete(outcome); + + assertTrue(future.isCancelled()); + } + + @Test + public void testCompleted() throws Exception { + CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>()); + handler.completed(TEXT); + assertTrue(result.isDone()); + assertSame(outcome, result.get()); + assertEquals(PolicyResult.FAILURE_RETRIES, outcome.getResult()); + assertEquals(TEXT, outcome.getMessage()); + } + + /** + * Tests completed() when doCompleted() throws an exception. + */ + @Test + public void testCompletedException() throws Exception { + IllegalStateException except = new IllegalStateException(); + + outcome = params.makeOutcome(); + handler = new MyHandler(params, outcome) { + @Override + protected OperationOutcome doComplete(String rawResponse) { + throw except; + } + }; + + CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>()); + handler.completed(TEXT); + assertTrue(result.isCompletedExceptionally()); + + AtomicReference<Throwable> thrown = new AtomicReference<>(); + result.whenComplete((unused, thrown2) -> thrown.set(thrown2)); + + assertSame(except, thrown.get()); + } + + @Test + public void testFailed() throws Exception { + IllegalStateException except = new IllegalStateException(); + + CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>()); + handler.failed(except); + + assertTrue(result.isDone()); + assertSame(outcome, result.get()); + assertEquals(PolicyResult.FAILURE_GUARD, outcome.getResult()); + } + + /** + * Tests failed() when doFailed() throws an exception. + */ + @Test + public void testFailedException() throws Exception { + IllegalStateException except = new IllegalStateException(); + + outcome = params.makeOutcome(); + handler = new MyHandler(params, outcome) { + @Override + protected OperationOutcome doFailed(Throwable thrown) { + throw except; + } + }; + + CompletableFuture<OperationOutcome> result = handler.handle(new CompletableFuture<>()); + handler.failed(except); + assertTrue(result.isCompletedExceptionally()); + + AtomicReference<Throwable> thrown = new AtomicReference<>(); + result.whenComplete((unused, thrown2) -> thrown.set(thrown2)); + + assertSame(except, thrown.get()); + } + + private class MyHandler extends AsyncResponseHandler<String> { + + public MyHandler(ControlLoopOperationParams params, OperationOutcome outcome) { + super(params, outcome); + } + + @Override + protected OperationOutcome doComplete(String rawResponse) { + OperationOutcome outcome = getOutcome(); + outcome.setResult(PolicyResult.FAILURE_RETRIES); + outcome.setMessage(rawResponse); + return outcome; + } + + @Override + protected OperationOutcome doFailed(Throwable thrown) { + OperationOutcome outcome = getOutcome(); + outcome.setResult(PolicyResult.FAILURE_GUARD); + return outcome; + } + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/CallbackManagerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/CallbackManagerTest.java new file mode 100644 index 000000000..44606cb14 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/CallbackManagerTest.java @@ -0,0 +1,89 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import org.junit.Before; +import org.junit.Test; + +public class CallbackManagerTest { + + private CallbackManager mgr; + + @Before + public void setUp() { + mgr = new CallbackManager(); + } + + @Test + public void testCanStart_testGetStartTime() { + // null until canXxx() is called + assertNull(mgr.getStartTime()); + + assertTrue(mgr.canStart()); + + Instant time = mgr.getStartTime(); + assertNotNull(time); + assertNull(mgr.getEndTime()); + + // false for now on + assertFalse(mgr.canStart()); + assertFalse(mgr.canStart()); + + assertEquals(time, mgr.getStartTime()); + } + + @Test + public void testCanEnd_testGetEndTime() { + // null until canXxx() is called + assertNull(mgr.getEndTime()); + assertNull(mgr.getEndTime()); + + assertTrue(mgr.canEnd()); + + Instant time = mgr.getEndTime(); + assertNotNull(time); + assertNull(mgr.getStartTime()); + + // false for now on + assertFalse(mgr.canEnd()); + assertFalse(mgr.canEnd()); + + assertEquals(time, mgr.getEndTime()); + } + + @Test + public void testRun() { + mgr.run(); + + assertNotNull(mgr.getStartTime()); + assertNotNull(mgr.getEndTime()); + + assertFalse(mgr.canStart()); + assertFalse(mgr.canEnd()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java new file mode 100644 index 000000000..5b9856f41 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java @@ -0,0 +1,93 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +public class DelayedIdentStringTest { + + private int countToStringCalls; + private Object object; + private DelayedIdentString delay; + + /** + * Initializes fields, including {@link #delay}. + */ + @Before + public void setUp() { + countToStringCalls = 0; + + object = new Object() { + @Override + public String toString() { + ++countToStringCalls; + return super.toString(); + } + }; + + delay = new DelayedIdentString(object); + } + + @Test + public void testToString() { + String delayed = delay.toString(); + assertEquals(1, countToStringCalls); + + String real = object.toString(); + assertNotEquals(real, delayed); + + assertThat(delayed).startsWith("@"); + assertTrue(delayed.length() > 1); + + // test case where the object is null + assertEquals(DelayedIdentString.NULL_STRING, new DelayedIdentString(null).toString()); + + // test case where the object returns null from toString() + object = new Object() { + @Override + public String toString() { + return null; + } + }; + assertEquals(DelayedIdentString.NULL_STRING, new DelayedIdentString(object).toString()); + + // test case where the object's toString() does not include "@" + object = new Object() { + @Override + public String toString() { + return "some text"; + } + }; + assertEquals(object.toString(), new DelayedIdentString(object).toString()); + } + + @Test + public void testDelayedIdentString() { + // should not have called the object's toString() method yet + assertEquals(0, countToStringCalls); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java index e9cf238e2..76cadffa6 100644 --- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java @@ -4,6 +4,7 @@ * ================================================================================ * Copyright (C) 2018 Ericsson. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. + * Modifications 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. @@ -23,10 +24,14 @@ package org.onap.policy.controlloop.actorserviceprovider; import java.util.ArrayList; import java.util.List; +import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl; -import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +public class DummyActor extends ActorImpl { + + public DummyActor() { + super(DummyActor.class.getSimpleName()); + } -public class DummyActor implements Actor { @Override public String actor() { return this.getClass().getSimpleName(); diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/OperationOutcomeTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/OperationOutcomeTest.java new file mode 100644 index 000000000..4e9728336 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/OperationOutcomeTest.java @@ -0,0 +1,137 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.time.Instant; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.PolicyResult; + +public class OperationOutcomeTest { + private static final String ACTOR = "my-actor"; + private static final String OPERATION = "my-operation"; + private static final String TARGET = "my-target"; + private static final Instant START = Instant.ofEpochMilli(10); + private static final Instant END = Instant.ofEpochMilli(20); + private static final String SUB_REQ_ID = "my-sub-request-id"; + private static final PolicyResult RESULT = PolicyResult.FAILURE_GUARD; + private static final String MESSAGE = "my-message"; + + private OperationOutcome outcome; + + @Before + public void setUp() { + outcome = new OperationOutcome(); + } + + @Test + public void testOperationOutcomeOperationOutcome() { + setAll(); + + OperationOutcome outcome2 = new OperationOutcome(outcome); + + assertEquals(ACTOR, outcome2.getActor()); + assertEquals(OPERATION, outcome2.getOperation()); + assertEquals(TARGET, outcome2.getTarget()); + assertEquals(START, outcome2.getStart()); + assertEquals(END, outcome2.getEnd()); + assertEquals(SUB_REQ_ID, outcome2.getSubRequestId()); + assertEquals(RESULT, outcome2.getResult()); + assertEquals(MESSAGE, outcome2.getMessage()); + } + + @Test + public void testToControlLoopOperation() { + setAll(); + + ControlLoopOperation outcome2 = outcome.toControlLoopOperation(); + + assertEquals(ACTOR, outcome2.getActor()); + assertEquals(OPERATION, outcome2.getOperation()); + assertEquals(TARGET, outcome2.getTarget()); + assertEquals(START, outcome2.getStart()); + assertEquals(END, outcome2.getEnd()); + assertEquals(SUB_REQ_ID, outcome2.getSubRequestId()); + assertEquals(RESULT.toString(), outcome2.getOutcome()); + assertEquals(MESSAGE, outcome2.getMessage()); + } + + /** + * Tests both isFor() methods, as one invokes the other. + */ + @Test + public void testIsFor() { + setAll(); + + // null case + assertFalse(OperationOutcome.isFor(null, ACTOR, OPERATION)); + + // actor mismatch + assertFalse(OperationOutcome.isFor(outcome, TARGET, OPERATION)); + + // operation mismatch + assertFalse(OperationOutcome.isFor(outcome, ACTOR, TARGET)); + + // null actor in outcome + outcome.setActor(null); + assertFalse(OperationOutcome.isFor(outcome, ACTOR, OPERATION)); + outcome.setActor(ACTOR); + + // null operation in outcome + outcome.setOperation(null); + assertFalse(OperationOutcome.isFor(outcome, ACTOR, OPERATION)); + outcome.setOperation(OPERATION); + + // null actor argument + assertThatThrownBy(() -> outcome.isFor(null, OPERATION)); + + // null operation argument + assertThatThrownBy(() -> outcome.isFor(ACTOR, null)); + + // true case + assertTrue(OperationOutcome.isFor(outcome, ACTOR, OPERATION)); + } + + @Test + public void testSetResult() { + outcome.setResult(PolicyResult.FAILURE_EXCEPTION); + assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult()); + + assertThatThrownBy(() -> outcome.setResult(null)); + } + + private void setAll() { + outcome.setActor(ACTOR); + outcome.setEnd(END); + outcome.setMessage(MESSAGE); + outcome.setOperation(OPERATION); + outcome.setResult(RESULT); + outcome.setStart(START); + outcome.setSubRequestId(SUB_REQ_ID); + outcome.setTarget(TARGET); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java new file mode 100644 index 000000000..4a3f321cf --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java @@ -0,0 +1,263 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import ch.qos.logback.classic.Logger; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; +import lombok.Builder; +import lombok.Data; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.test.log.logback.ExtractAppender; +import org.slf4j.LoggerFactory; + +public class UtilTest { + private static final String MY_REQUEST = "my-request"; + private static final String URL = "my-url"; + private static final String OUT_URL = "OUT|REST|my-url"; + private static final String IN_URL = "IN|REST|my-url"; + protected static final String EXPECTED_EXCEPTION = "expected exception"; + + /** + * Used to attach an appender to the class' logger. + */ + private static final Logger logger = (Logger) LoggerFactory.getLogger(Util.class); + private static final ExtractAppender appender = new ExtractAppender(); + + /** + * Initializes statics. + */ + @BeforeClass + public static void setUpBeforeClass() { + appender.setContext(logger.getLoggerContext()); + appender.start(); + + logger.addAppender(appender); + } + + @AfterClass + public static void tearDownAfterClass() { + appender.stop(); + } + + @Before + public void setUp() { + appender.clearExtractions(); + } + + @Test + public void testIdent() { + Object object = new Object(); + String result = Util.ident(object).toString(); + + assertNotEquals(object.toString(), result); + assertThat(result).startsWith("@"); + assertTrue(result.length() > 1); + } + + @Test + public void testLogRestRequest() throws CoderException { + // log structured data + appender.clearExtractions(); + Util.logRestRequest(URL, new Abc(10, null, null)); + List<String> output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(OUT_URL).contains("{\n \"intValue\": 10\n}"); + + // log a plain string + appender.clearExtractions(); + Util.logRestRequest(URL, MY_REQUEST); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(OUT_URL).contains(MY_REQUEST); + + // exception from coder + StandardCoder coder = new StandardCoder() { + @Override + public String encode(Object object, boolean pretty) throws CoderException { + throw new CoderException(EXPECTED_EXCEPTION); + } + }; + + appender.clearExtractions(); + Util.logRestRequest(coder, URL, new Abc(11, null, null)); + output = appender.getExtracted(); + assertEquals(2, output.size()); + assertThat(output.get(0)).contains("cannot pretty-print request"); + assertThat(output.get(1)).contains(OUT_URL); + } + + @Test + public void testLogRestResponse() throws CoderException { + // log structured data + appender.clearExtractions(); + Util.logRestResponse(URL, new Abc(10, null, null)); + List<String> output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(IN_URL).contains("{\n \"intValue\": 10\n}"); + + // log null response + appender.clearExtractions(); + Util.logRestResponse(URL, null); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(IN_URL).contains("null"); + + // log a plain string + appender.clearExtractions(); + Util.logRestResponse(URL, MY_REQUEST); + output = appender.getExtracted(); + assertEquals(1, output.size()); + + assertThat(output.get(0)).contains(IN_URL).contains(MY_REQUEST); + + // exception from coder + StandardCoder coder = new StandardCoder() { + @Override + public String encode(Object object, boolean pretty) throws CoderException { + throw new CoderException(EXPECTED_EXCEPTION); + } + }; + + appender.clearExtractions(); + Util.logRestResponse(coder, URL, new Abc(11, null, null)); + output = appender.getExtracted(); + assertEquals(2, output.size()); + assertThat(output.get(0)).contains("cannot pretty-print response"); + assertThat(output.get(1)).contains(IN_URL); + } + + @Test + public void testRunFunction() { + // no exception, no log + AtomicInteger count = new AtomicInteger(); + Util.runFunction(() -> count.incrementAndGet(), "no error"); + assertEquals(1, count.get()); + assertEquals(0, appender.getExtracted().size()); + + // with an exception + Runnable runnable = () -> { + count.incrementAndGet(); + throw new IllegalStateException("expected exception"); + }; + + appender.clearExtractions(); + Util.runFunction(runnable, "error with no args"); + List<String> output = appender.getExtracted(); + assertEquals(1, output.size()); + assertThat(output.get(0)).contains("error with no args"); + + appender.clearExtractions(); + Util.runFunction(runnable, "error {} {} arg(s)", "with", 2); + output = appender.getExtracted(); + assertEquals(1, output.size()); + assertThat(output.get(0)).contains("error with 2 arg(s)"); + } + + @Test + public void testTranslate() { + // Abc => Abc + final Abc abc = Abc.builder().intValue(1).strValue("hello").anotherString("another").build(); + Abc abc2 = Util.translate("abc to abc", abc, Abc.class); + assertEquals(abc, abc2); + + // Abc => Similar + Similar sim = Util.translate("abc to similar", abc, Similar.class); + assertEquals(abc.getIntValue(), sim.getIntValue()); + assertEquals(abc.getStrValue(), sim.getStrValue()); + + // Abc => Map + @SuppressWarnings("unchecked") + Map<String, Object> map = Util.translate("abc to map", abc, TreeMap.class); + assertEquals("{anotherString=another, intValue=1, strValue=hello}", map.toString()); + + // Map => Map + @SuppressWarnings("unchecked") + Map<String, Object> map2 = Util.translate("map to map", map, LinkedHashMap.class); + assertEquals(map.toString(), map2.toString()); + + // Map => Abc + abc2 = Util.translate("map to abc", map, Abc.class); + assertEquals(abc, abc2); + } + + @Test + public void testTranslateToMap() { + assertNull(Util.translateToMap("map: null", null)); + + // Abc => Map + final Abc abc = Abc.builder().intValue(2).strValue("world").anotherString("some").build(); + Map<String, Object> map = new TreeMap<>(Util.translateToMap("map: abc to map", abc)); + assertEquals("{anotherString=some, intValue=2, strValue=world}", map.toString()); + + // Map => Map + Map<String, Object> map2 = Util.translateToMap("map: map to map", map); + assertEquals(map.toString(), map2.toString()); + + assertThatIllegalArgumentException().isThrownBy(() -> Util.translateToMap("map: string", "some string")) + .withMessageContaining("map: string"); + } + + @Data + @Builder + public static class Abc { + private int intValue; + private String strValue; + private String anotherString; + } + + // this shares some fields with Abc so the data should transfer + @Data + @Builder + public static class Similar { + private int intValue; + private String strValue; + } + + // throws an exception when getXxx() is used + public static class DataWithException { + @SuppressWarnings("unused") + private int intValue; + + public int getIntValue() { + throw new IllegalStateException(); + } + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java new file mode 100644 index 000000000..0d917ad3e --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java @@ -0,0 +1,87 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.controlloop; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.util.Map; +import java.util.UUID; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.controlloop.VirtualControlLoopEvent; + +public class ControlLoopEventContextTest { + private static final UUID REQ_ID = UUID.randomUUID(); + + private Map<String, String> enrichment; + private VirtualControlLoopEvent event; + private ControlLoopEventContext context; + + /** + * Initializes data, including {@link #context}. + */ + @Before + public void setUp() { + enrichment = Map.of("abc", "one", "def", "two"); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + event.setAai(enrichment); + + context = new ControlLoopEventContext(event); + } + + @Test + public void testControlLoopEventContext() { + assertSame(event, context.getEvent()); + assertSame(REQ_ID, context.getRequestId()); + assertEquals(enrichment, context.getEnrichment()); + + // null event + assertThatThrownBy(() -> new ControlLoopEventContext(null)); + + // no request id, no enrichment data + event.setRequestId(null); + event.setAai(null); + context = new ControlLoopEventContext(event); + assertSame(event, context.getEvent()); + assertNotNull(context.getRequestId()); + assertEquals(Map.of(), context.getEnrichment()); + } + + @Test + public void testContains_testGetProperty_testSetProperty() { + context.setProperty("abc", "a string"); + context.setProperty("def", 100); + + assertFalse(context.contains("ghi")); + + String strValue = context.getProperty("abc"); + assertEquals("a string", strValue); + + int intValue = context.getProperty("def"); + assertEquals(100, intValue); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java new file mode 100644 index 000000000..a209fb0d8 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java @@ -0,0 +1,384 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.impl; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ObjectValidationResult; +import org.onap.policy.common.parameters.ValidationStatus; +import org.onap.policy.controlloop.actorserviceprovider.Operator; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; + +public class ActorImplTest { + private static final String EXPECTED_EXCEPTION = "expected exception"; + private static final String ACTOR_NAME = "my-actor"; + private static final String OPER1 = "add"; + private static final String OPER2 = "subtract"; + private static final String OPER3 = "multiply"; + private static final String OPER4 = "divide"; + + private MyOper oper1; + private MyOper oper2; + private MyOper oper3; + private MyOper oper4; + + private Map<String, Object> sub1; + private Map<String, Object> sub2; + private Map<String, Object> sub3; + private Map<String, Object> sub4; + private Map<String, Object> params; + + private ActorImpl actor; + + + /** + * Initializes the fields, including a fully populated {@link #actor}. + */ + @Before + public void setUp() { + oper1 = spy(new MyOper(OPER1)); + oper2 = spy(new MyOper(OPER2)); + oper3 = spy(new MyOper(OPER3)); + oper4 = spy(new MyOper(OPER4)); + + sub1 = Map.of("sub A", "value A"); + sub2 = Map.of("sub B", "value B"); + sub3 = Map.of("sub C", "value C"); + sub4 = Map.of("sub D", "value D"); + + params = Map.of(OPER1, sub1, OPER2, sub2, OPER3, sub3, OPER4, sub4); + + actor = makeActor(oper1, oper2, oper3, oper4); + } + + @Test + public void testActorImpl_testGetName() { + assertEquals(ACTOR_NAME, actor.getName()); + assertEquals(4, actor.getOperationNames().size()); + } + + @Test + public void testDoStart() { + actor.configure(params); + assertEquals(4, actor.getOperationNames().size()); + + /* + * arrange for second operator to be unconfigured and the third operator to throw + * an exception + */ + Iterator<Operator> iter = actor.getOperators().iterator(); + iter.next(); + when(iter.next().isConfigured()).thenReturn(false); + when(iter.next().start()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION)); + + /* + * Start the actor. + */ + actor.start(); + assertTrue(actor.isAlive()); + + iter = actor.getOperators().iterator(); + verify(iter.next()).start(); + // this one isn't configured, so shouldn't attempt to start it + verify(iter.next(), never()).start(); + // this one threw an exception + iter.next(); + verify(iter.next()).start(); + + // no other types of operations + verify(oper1, never()).stop(); + verify(oper1, never()).shutdown(); + } + + @Test + public void testDoStop() { + actor.configure(params); + actor.start(); + assertEquals(4, actor.getOperationNames().size()); + + // arrange for second operator to throw an exception + Iterator<Operator> iter = actor.getOperators().iterator(); + iter.next(); + when(iter.next().stop()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION)); + + /* + * Stop the actor. + */ + actor.stop(); + assertFalse(actor.isAlive()); + + iter = actor.getOperators().iterator(); + verify(iter.next()).stop(); + // this one threw an exception + iter.next(); + verify(iter.next()).stop(); + verify(iter.next()).stop(); + + // no additional types of operations + verify(oper1).configure(any()); + verify(oper1).start(); + + // no other types of operation + verify(oper1, never()).shutdown(); + } + + @Test + public void testDoShutdown() { + actor.configure(params); + actor.start(); + assertEquals(4, actor.getOperationNames().size()); + + // arrange for second operator to throw an exception + Iterator<Operator> iter = actor.getOperators().iterator(); + iter.next(); + doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(iter.next()).shutdown(); + + /* + * Stop the actor. + */ + actor.shutdown(); + assertFalse(actor.isAlive()); + + iter = actor.getOperators().iterator(); + verify(iter.next()).shutdown(); + // this one threw an exception + iter.next(); + verify(iter.next()).shutdown(); + verify(iter.next()).shutdown(); + + // no additional types of operations + verify(oper1).configure(any()); + verify(oper1).start(); + + // no other types of operation + verify(oper1, never()).stop(); + } + + @Test + public void testAddOperator() { + // cannot add operators if already configured + actor.configure(params); + assertThatIllegalStateException().isThrownBy(() -> actor.addOperator(oper1)); + + /* + * make an actor where operators two and four have names that are duplicates of + * the others + */ + oper2 = spy(new MyOper(OPER1)); + oper4 = spy(new MyOper(OPER3)); + + actor = makeActor(oper1, oper2, oper3, oper4); + + assertEquals(2, actor.getOperationNames().size()); + + assertSame(oper1, actor.getOperator(OPER1)); + assertSame(oper3, actor.getOperator(OPER3)); + } + + @Test + public void testGetOperator() { + assertSame(oper1, actor.getOperator(OPER1)); + assertSame(oper3, actor.getOperator(OPER3)); + + assertThatIllegalArgumentException().isThrownBy(() -> actor.getOperator("unknown name")); + } + + @Test + public void testGetOperators() { + // @formatter:off + assertEquals("[add, divide, multiply, subtract]", + actor.getOperators().stream() + .map(Operator::getName) + .sorted() + .collect(Collectors.toList()) + .toString()); + // @formatter:on + } + + @Test + public void testGetOperationNames() { + // @formatter:off + assertEquals("[add, divide, multiply, subtract]", + actor.getOperationNames().stream() + .sorted() + .collect(Collectors.toList()) + .toString()); + // @formatter:on + } + + @Test + public void testDoConfigure() { + actor.configure(params); + assertTrue(actor.isConfigured()); + + verify(oper1).configure(sub1); + verify(oper2).configure(sub2); + verify(oper3).configure(sub3); + verify(oper4).configure(sub4); + + // no other types of operations + verify(oper1, never()).start(); + verify(oper1, never()).stop(); + verify(oper1, never()).shutdown(); + } + + /** + * Tests doConfigure() where operators throw parameter validation and runtime + * exceptions. + */ + @Test + public void testDoConfigureExceptions() { + makeValidException(oper1); + makeRuntimeException(oper2); + makeValidException(oper3); + + actor.configure(params); + assertTrue(actor.isConfigured()); + } + + /** + * Tests doConfigure(). Arranges for the following: + * <ul> + * <li>one operator is configured, but has parameters</li> + * <li>another operator is configured, but has no parameters</li> + * <li>another operator has no parameters and is not configured</li> + * </ul> + */ + @Test + public void testDoConfigureConfigure() { + // need mutable parameters + params = new TreeMap<>(params); + + // configure one operator + oper1.configure(sub1); + + // configure another and remove its parameters + oper2.configure(sub2); + params.remove(OPER2); + + // create a new, unconfigured actor + Operator oper5 = spy(new MyOper("UNCONFIGURED")); + actor = makeActor(oper1, oper2, oper3, oper4, oper5); + + /* + * Configure it. + */ + actor.configure(params); + assertTrue(actor.isConfigured()); + + // this should have been configured again + verify(oper1, times(2)).configure(sub1); + + // no parameters, so this should not have been configured again + verify(oper2).configure(sub2); + + // these were only configured once + verify(oper3).configure(sub3); + verify(oper4).configure(sub4); + + // never configured + verify(oper5, never()).configure(any()); + assertFalse(oper5.isConfigured()); + + // start and verify that all are started except for the last + actor.start(); + verify(oper1).start(); + verify(oper2).start(); + verify(oper3).start(); + verify(oper4).start(); + verify(oper5, never()).start(); + } + + /** + * Arranges for an operator to throw a validation exception when + * {@link Operator#configure(Map)} is invoked. + * + * @param oper operator of interest + */ + private void makeValidException(Operator oper) { + ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException( + new ObjectValidationResult(actor.getName(), null, ValidationStatus.INVALID, "null")); + doThrow(ex).when(oper).configure(any()); + } + + /** + * Arranges for an operator to throw a runtime exception when + * {@link Operator#configure(Map)} is invoked. + * + * @param oper operator of interest + */ + private void makeRuntimeException(Operator oper) { + IllegalStateException ex = new IllegalStateException(EXPECTED_EXCEPTION); + doThrow(ex).when(oper).configure(any()); + } + + @Test + public void testMakeOperatorParameters() { + actor.configure(params); + + // each operator should have received its own parameters + verify(oper1).configure(sub1); + verify(oper2).configure(sub2); + verify(oper3).configure(sub3); + verify(oper4).configure(sub4); + } + + /** + * Makes an actor with the given operators. + * + * @param operators associated operators + * @return a new actor + */ + private ActorImpl makeActor(Operator... operators) { + ActorImpl actor = new ActorImpl(ACTOR_NAME); + + for (Operator oper : operators) { + actor.addOperator(oper); + } + + return actor; + } + + private static class MyOper extends OperatorPartial implements Operator { + + public MyOper(String name) { + super(ACTOR_NAME, name); + } + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java new file mode 100644 index 000000000..2da789989 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpActorTest.java @@ -0,0 +1,81 @@ +/*- + * ============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.assertNull; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpActorParams; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; + +public class HttpActorTest { + + private static final String ACTOR = "my-actor"; + private static final String UNKNOWN = "unknown"; + private static final String CLIENT = "my-client"; + private static final long TIMEOUT = 10L; + + private HttpActor actor; + + @Before + public void setUp() { + actor = new HttpActor(ACTOR); + } + + @Test + public void testMakeOperatorParameters() { + HttpActorParams params = new HttpActorParams(); + params.setClientName(CLIENT); + params.setTimeoutSec(TIMEOUT); + params.setPath(Map.of("operA", "urlA", "operB", "urlB")); + + final HttpActor prov = new HttpActor(ACTOR); + Function<String, Map<String, Object>> maker = + prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params)); + + assertNull(maker.apply(UNKNOWN)); + + // use a TreeMap to ensure the properties are sorted + assertEquals("{clientName=my-client, path=urlA, timeoutSec=10}", + new TreeMap<>(maker.apply("operA")).toString()); + + assertEquals("{clientName=my-client, path=urlB, timeoutSec=10}", + new TreeMap<>(maker.apply("operB")).toString()); + + // with invalid actor parameters + params.setClientName(null); + assertThatThrownBy(() -> prov.makeOperatorParameters(Util.translateToMap(prov.getName(), params))) + .isInstanceOf(ParameterValidationRuntimeException.class); + } + + @Test + public void testHttpActor() { + assertEquals(ACTOR, actor.getName()); + assertEquals(ACTOR, actor.getFullName()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java new file mode 100644 index 000000000..c006cf333 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/HttpOperatorTest.java @@ -0,0 +1,104 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.impl; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +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.http.client.HttpClient; +import org.onap.policy.common.endpoints.http.client.HttpClientFactory; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; + +public class HttpOperatorTest { + + private static final String ACTOR = "my-actor"; + private static final String OPERATION = "my-name"; + private static final String CLIENT = "my-client"; + private static final String PATH = "my-path"; + private static final long TIMEOUT = 100; + + @Mock + private HttpClient client; + + private HttpOperator oper; + + /** + * Initializes fields, including {@link #oper}. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + oper = new HttpOperator(ACTOR, OPERATION); + } + + @Test + public void testDoConfigureMapOfStringObject_testGetClient_testGetPath_testGetTimeoutSec() { + assertNull(oper.getClient()); + assertNull(oper.getPath()); + assertEquals(0L, oper.getTimeoutSec()); + + oper = new HttpOperator(ACTOR, OPERATION) { + @Override + protected HttpClientFactory getClientFactory() { + HttpClientFactory factory = mock(HttpClientFactory.class); + when(factory.get(CLIENT)).thenReturn(client); + return factory; + } + }; + + HttpParams params = HttpParams.builder().clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build(); + Map<String, Object> paramMap = Util.translateToMap(OPERATION, params); + oper.configure(paramMap); + + assertSame(client, oper.getClient()); + assertEquals(PATH, oper.getPath()); + assertEquals(TIMEOUT, oper.getTimeoutSec()); + + // test invalid parameters + paramMap.remove("path"); + assertThatThrownBy(() -> oper.configure(paramMap)).isInstanceOf(ParameterValidationRuntimeException.class); + } + + @Test + public void testHttpOperator() { + assertEquals(ACTOR, oper.getActorName()); + assertEquals(OPERATION, oper.getName()); + assertEquals(ACTOR + "." + OPERATION, oper.getFullName()); + } + + @Test + public void testGetClient() { + assertNotNull(oper.getClientFactory()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java new file mode 100644 index 000000000..21bc656f2 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java @@ -0,0 +1,1290 @@ +/*- + * ============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.assertThatIllegalStateException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.time.Instant; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Queue; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import lombok.Getter; +import lombok.Setter; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineControllerFuture; +import org.onap.policy.controlloop.policy.PolicyResult; + +public class OperatorPartialTest { + private static final int MAX_PARALLEL_REQUESTS = 10; + private static final String EXPECTED_EXCEPTION = "expected exception"; + private static final String ACTOR = "my-actor"; + private static final String OPERATOR = "my-operator"; + private static final String TARGET = "my-target"; + private static final int TIMEOUT = 1000; + private static final UUID REQ_ID = UUID.randomUUID(); + + private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream() + .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList()); + + private VirtualControlLoopEvent event; + private Map<String, Object> config; + private ControlLoopEventContext context; + private MyExec executor; + private ControlLoopOperationParams params; + + private MyOper oper; + + private int numStart; + private int numEnd; + + private Instant tstart; + + private OperationOutcome opstart; + private OperationOutcome opend; + + /** + * Initializes the fields, including {@link #oper}. + */ + @Before + public void setUp() { + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + + config = new TreeMap<>(); + context = new ControlLoopEventContext(event); + executor = new MyExec(); + + params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context) + .executor(executor).actor(ACTOR).operation(OPERATOR).timeoutSec(TIMEOUT) + .startCallback(this::starter).targetEntity(TARGET).build(); + + oper = new MyOper(); + oper.configure(new TreeMap<>()); + oper.start(); + + tstart = null; + + opstart = null; + opend = null; + } + + @Test + public void testOperatorPartial_testGetActorName_testGetName() { + assertEquals(ACTOR, oper.getActorName()); + assertEquals(OPERATOR, oper.getName()); + assertEquals(ACTOR + "." + OPERATOR, oper.getFullName()); + } + + @Test + public void testGetBlockingExecutor() throws InterruptedException { + CountDownLatch latch = new CountDownLatch(1); + + /* + * Use an operator that doesn't override getBlockingExecutor(). + */ + OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {}; + oper2.getBlockingExecutor().execute(() -> latch.countDown()); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + + @Test + public void testDoConfigure() { + oper = spy(new MyOper()); + + oper.configure(config); + verify(oper).configure(config); + + // repeat - SHOULD be run again + oper.configure(config); + verify(oper, times(2)).configure(config); + } + + @Test + public void testDoStart() { + oper = spy(new MyOper()); + + oper.configure(config); + oper.start(); + + verify(oper).doStart(); + + // others should not have been invoked + verify(oper, never()).doStop(); + verify(oper, never()).doShutdown(); + } + + @Test + public void testDoStop() { + oper = spy(new MyOper()); + + oper.configure(config); + oper.start(); + oper.stop(); + + verify(oper).doStop(); + + // should not have been re-invoked + verify(oper).doStart(); + + // others should not have been invoked + verify(oper, never()).doShutdown(); + } + + @Test + public void testDoShutdown() { + oper = spy(new MyOper()); + + oper.configure(config); + oper.start(); + oper.shutdown(); + + verify(oper).doShutdown(); + + // should not have been re-invoked + verify(oper).doStart(); + + // others should not have been invoked + verify(oper, never()).doStop(); + } + + @Test + public void testStartOperation() { + verifyRun("testStartOperation", 1, 1, PolicyResult.SUCCESS); + } + + /** + * Tests startOperation() when the operator is not running. + */ + @Test + public void testStartOperationNotRunning() { + // use a new operator, one that hasn't been started yet + oper = new MyOper(); + oper.configure(new TreeMap<>()); + + assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params)); + } + + /** + * Tests startOperation() when the operation has a preprocessor. + */ + @Test + public void testStartOperationWithPreprocessor() { + AtomicInteger count = new AtomicInteger(); + + CompletableFuture<OperationOutcome> preproc = CompletableFuture.supplyAsync(() -> { + count.incrementAndGet(); + return makeSuccess(); + }, executor); + + oper.setPreProcessor(preproc); + + verifyRun("testStartOperationWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS); + + assertEquals(1, count.get()); + } + + /** + * Tests startOperation() with multiple running requests. + */ + @Test + public void testStartOperationMultiple() { + for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) { + oper.startOperation(params); + } + + assertTrue(executor.runAll()); + + assertNotNull(opstart); + assertNotNull(opend); + assertEquals(PolicyResult.SUCCESS, opend.getResult()); + + assertEquals(MAX_PARALLEL_REQUESTS, numStart); + assertEquals(MAX_PARALLEL_REQUESTS, oper.getCount()); + assertEquals(MAX_PARALLEL_REQUESTS, numEnd); + } + + /** + * Tests startPreprocessor() when the preprocessor returns a failure. + */ + @Test + public void testStartPreprocessorFailure() { + oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure())); + + verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD); + } + + /** + * Tests startPreprocessor() when the preprocessor throws an exception. + */ + @Test + public void testStartPreprocessorException() { + // arrange for the preprocessor to throw an exception + oper.setPreProcessor(CompletableFuture.failedFuture(new IllegalStateException(EXPECTED_EXCEPTION))); + + verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD); + } + + /** + * Tests startPreprocessor() when the pipeline is not running. + */ + @Test + public void testStartPreprocessorNotRunning() { + // arrange for the preprocessor to return success, which will be ignored + oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess())); + + oper.startOperation(params).cancel(false); + assertTrue(executor.runAll()); + + assertNull(opstart); + assertNull(opend); + + assertEquals(0, numStart); + assertEquals(0, oper.getCount()); + assertEquals(0, numEnd); + } + + /** + * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception. + */ + @Test + public void testStartPreprocessorBuilderException() { + oper = new MyOper() { + @Override + protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) { + throw new IllegalStateException(EXPECTED_EXCEPTION); + } + }; + + oper.configure(new TreeMap<>()); + oper.start(); + + assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params)); + + // should be nothing in the queue + assertEquals(0, executor.getQueueLength()); + } + + @Test + public void testStartPreprocessorAsync() { + assertNull(oper.startPreprocessorAsync(params)); + } + + @Test + public void testStartOperationAsync() { + oper.startOperation(params); + assertTrue(executor.runAll()); + + assertEquals(1, oper.getCount()); + } + + @Test + public void testIsSuccess() { + OperationOutcome outcome = new OperationOutcome(); + + outcome.setResult(PolicyResult.SUCCESS); + assertTrue(oper.isSuccess(outcome)); + + for (PolicyResult failure : FAILURE_RESULTS) { + outcome.setResult(failure); + assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome)); + } + } + + @Test + public void testIsActorFailed() { + assertFalse(oper.isActorFailed(null)); + + OperationOutcome outcome = params.makeOutcome(); + + // incorrect outcome + outcome.setResult(PolicyResult.SUCCESS); + assertFalse(oper.isActorFailed(outcome)); + + outcome.setResult(PolicyResult.FAILURE_RETRIES); + assertFalse(oper.isActorFailed(outcome)); + + // correct outcome + outcome.setResult(PolicyResult.FAILURE); + + // incorrect actor + outcome.setActor(TARGET); + assertFalse(oper.isActorFailed(outcome)); + outcome.setActor(null); + assertFalse(oper.isActorFailed(outcome)); + outcome.setActor(ACTOR); + + // incorrect operation + outcome.setOperation(TARGET); + assertFalse(oper.isActorFailed(outcome)); + outcome.setOperation(null); + assertFalse(oper.isActorFailed(outcome)); + outcome.setOperation(OPERATOR); + + // correct values + assertTrue(oper.isActorFailed(outcome)); + } + + @Test + public void testDoOperation() { + /* + * Use an operator that doesn't override doOperation(). + */ + OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) { + @Override + protected Executor getBlockingExecutor() { + return executor; + } + }; + + oper2.configure(new TreeMap<>()); + oper2.start(); + + oper2.startOperation(params); + assertTrue(executor.runAll()); + + assertNotNull(opend); + assertEquals(PolicyResult.FAILURE_EXCEPTION, opend.getResult()); + } + + @Test + public void testTimeout() throws Exception { + + // use a real executor + params = params.toBuilder().executor(ForkJoinPool.commonPool()).build(); + + // trigger timeout very quickly + oper = new MyOper() { + @Override + protected long getTimeOutMillis(Integer timeoutSec) { + return 1; + } + + @Override + protected CompletableFuture<OperationOutcome> startOperationAsync(ControlLoopOperationParams params, + int attempt, OperationOutcome outcome) { + + OperationOutcome outcome2 = params.makeOutcome(); + outcome2.setResult(PolicyResult.SUCCESS); + + /* + * Create an incomplete future that will timeout after the operation's + * timeout. If it fires before the other timer, then it will return a + * SUCCESS outcome. + */ + CompletableFuture<OperationOutcome> future = new CompletableFuture<>(); + future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome, + params.getExecutor()); + + return future; + } + }; + + oper.configure(new TreeMap<>()); + oper.start(); + + assertEquals(PolicyResult.FAILURE_TIMEOUT, oper.startOperation(params).get().getResult()); + } + + /** + * Verifies that the timer doesn't encompass the preprocessor and doesn't stop the + * operation once the preprocessor completes. + */ + @Test + public void testTimeoutInPreprocessor() throws Exception { + + // use a real executor + params = params.toBuilder().executor(ForkJoinPool.commonPool()).build(); + + // trigger timeout very quickly + oper = new MyOper() { + @Override + protected long getTimeOutMillis(Integer timeoutSec) { + return 10; + } + + @Override + protected Executor getBlockingExecutor() { + return command -> { + Thread thread = new Thread(command); + thread.start(); + }; + } + + @Override + protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) { + + OperationOutcome outcome = makeSuccess(); + + /* + * Create an incomplete future that will timeout after the operation's + * timeout. If it fires before the other timer, then it will return a + * SUCCESS outcome. + */ + CompletableFuture<OperationOutcome> future = new CompletableFuture<>(); + future = future.orTimeout(200, TimeUnit.MILLISECONDS).handleAsync((unused1, unused2) -> outcome, + params.getExecutor()); + + return future; + } + }; + + oper.configure(new TreeMap<>()); + oper.start(); + + OperationOutcome result = oper.startOperation(params).get(); + assertEquals(PolicyResult.SUCCESS, result.getResult()); + + assertNotNull(opstart); + assertNotNull(opend); + assertEquals(PolicyResult.SUCCESS, opend.getResult()); + + assertEquals(1, numStart); + assertEquals(1, oper.getCount()); + assertEquals(1, numEnd); + } + + /** + * Tests retry functions, when the count is set to zero and retries are exhausted. + */ + @Test + public void testSetRetryFlag_testRetryOnFailure_ZeroRetries_testStartOperationAttempt() { + params = params.toBuilder().retry(0).build(); + oper.setMaxFailures(10); + + verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE); + } + + /** + * Tests retry functions, when the count is null and retries are exhausted. + */ + @Test + public void testSetRetryFlag_testRetryOnFailure_NullRetries() { + params = params.toBuilder().retry(null).build(); + oper.setMaxFailures(10); + + verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE); + } + + /** + * Tests retry functions, when retries are exhausted. + */ + @Test + public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() { + final int maxRetries = 3; + params = params.toBuilder().retry(maxRetries).build(); + oper.setMaxFailures(10); + + verifyRun("testSetRetryFlag_testRetryOnFailure_RetriesExhausted", maxRetries + 1, maxRetries + 1, + PolicyResult.FAILURE_RETRIES); + } + + /** + * Tests retry functions, when a success follows some retries. + */ + @Test + public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() { + params = params.toBuilder().retry(10).build(); + + final int maxFailures = 3; + oper.setMaxFailures(maxFailures); + + verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1, + PolicyResult.SUCCESS); + } + + /** + * Tests retry functions, when the outcome is {@code null}. + */ + @Test + public void testSetRetryFlag_testRetryOnFailure_NullOutcome() { + + // arrange to return null from doOperation() + oper = new MyOper() { + @Override + protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt, + OperationOutcome operation) { + + // update counters + super.doOperation(params, attempt, operation); + return null; + } + }; + + oper.configure(new TreeMap<>()); + oper.start(); + + verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop()); + } + + @Test + public void testIsSameOperation() { + assertFalse(oper.isSameOperation(null)); + + OperationOutcome outcome = params.makeOutcome(); + + // wrong actor - should be false + outcome.setActor(null); + assertFalse(oper.isSameOperation(outcome)); + outcome.setActor(TARGET); + assertFalse(oper.isSameOperation(outcome)); + outcome.setActor(ACTOR); + + // wrong operation - should be null + outcome.setOperation(null); + assertFalse(oper.isSameOperation(outcome)); + outcome.setOperation(TARGET); + assertFalse(oper.isSameOperation(outcome)); + outcome.setOperation(OPERATOR); + + assertTrue(oper.isSameOperation(outcome)); + } + + /** + * Tests handleFailure() when the outcome is a success. + */ + @Test + public void testHandlePreprocessorFailureTrue() { + oper.setPreProcessor(CompletableFuture.completedFuture(makeSuccess())); + verifyRun("testHandlePreprocessorFailureTrue", 1, 1, PolicyResult.SUCCESS); + } + + /** + * Tests handleFailure() when the outcome is <i>not</i> a success. + */ + @Test + public void testHandlePreprocessorFailureFalse() throws Exception { + oper.setPreProcessor(CompletableFuture.completedFuture(makeFailure())); + verifyRun("testHandlePreprocessorFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD); + } + + /** + * Tests handleFailure() when the outcome is {@code null}. + */ + @Test + public void testHandlePreprocessorFailureNull() throws Exception { + // arrange to return null from the preprocessor + oper.setPreProcessor(CompletableFuture.completedFuture(null)); + + verifyRun("testHandlePreprocessorFailureNull", 1, 0, PolicyResult.FAILURE_GUARD); + } + + @Test + public void testFromException() { + // arrange to generate an exception when operation runs + oper.setGenException(true); + + verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION); + } + + /** + * Tests fromException() when there is no exception. + */ + @Test + public void testFromExceptionNoExcept() { + verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS); + } + + /** + * Tests both flavors of anyOf(), because one invokes the other. + */ + @Test + public void testAnyOf() throws Exception { + // first task completes, others do not + List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + + final OperationOutcome outcome = params.makeOutcome(); + + tasks.add(CompletableFuture.completedFuture(outcome)); + tasks.add(new CompletableFuture<>()); + tasks.add(new CompletableFuture<>()); + + CompletableFuture<OperationOutcome> result = oper.anyOf(params, tasks); + assertTrue(executor.runAll()); + + assertTrue(result.isDone()); + assertSame(outcome, result.get()); + + // second task completes, others do not + tasks = new LinkedList<>(); + + tasks.add(new CompletableFuture<>()); + tasks.add(CompletableFuture.completedFuture(outcome)); + tasks.add(new CompletableFuture<>()); + + result = oper.anyOf(params, tasks); + assertTrue(executor.runAll()); + + assertTrue(result.isDone()); + assertSame(outcome, result.get()); + + // third task completes, others do not + tasks = new LinkedList<>(); + + tasks.add(new CompletableFuture<>()); + tasks.add(new CompletableFuture<>()); + tasks.add(CompletableFuture.completedFuture(outcome)); + + result = oper.anyOf(params, tasks); + assertTrue(executor.runAll()); + + assertTrue(result.isDone()); + assertSame(outcome, result.get()); + } + + /** + * Tests both flavors of allOf(), because one invokes the other. + */ + @Test + public void testAllOf() throws Exception { + List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + + final OperationOutcome outcome = params.makeOutcome(); + + CompletableFuture<OperationOutcome> future1 = new CompletableFuture<>(); + CompletableFuture<OperationOutcome> future2 = new CompletableFuture<>(); + CompletableFuture<OperationOutcome> future3 = new CompletableFuture<>(); + + tasks.add(future1); + tasks.add(future2); + tasks.add(future3); + + CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks); + + assertTrue(executor.runAll()); + assertFalse(result.isDone()); + future1.complete(outcome); + + // complete 3 before 2 + assertTrue(executor.runAll()); + assertFalse(result.isDone()); + future3.complete(outcome); + + assertTrue(executor.runAll()); + assertFalse(result.isDone()); + future2.complete(outcome); + + // all of them are now done + assertTrue(executor.runAll()); + assertTrue(result.isDone()); + assertSame(outcome, result.get()); + } + + @Test + public void testCombineOutcomes() throws Exception { + // only one outcome + verifyOutcomes(0, PolicyResult.SUCCESS); + verifyOutcomes(0, PolicyResult.FAILURE_EXCEPTION); + + // maximum is in different positions + verifyOutcomes(0, PolicyResult.FAILURE, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD); + verifyOutcomes(1, PolicyResult.SUCCESS, PolicyResult.FAILURE, PolicyResult.FAILURE_GUARD); + verifyOutcomes(2, PolicyResult.SUCCESS, PolicyResult.FAILURE_GUARD, PolicyResult.FAILURE); + + // null outcome + final List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + tasks.add(CompletableFuture.completedFuture(null)); + CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks); + + assertTrue(executor.runAll()); + assertTrue(result.isDone()); + assertNull(result.get()); + + // one throws an exception during execution + IllegalStateException except = new IllegalStateException(EXPECTED_EXCEPTION); + + tasks.clear(); + tasks.add(CompletableFuture.completedFuture(params.makeOutcome())); + tasks.add(CompletableFuture.failedFuture(except)); + tasks.add(CompletableFuture.completedFuture(params.makeOutcome())); + result = oper.allOf(params, tasks); + + assertTrue(executor.runAll()); + assertTrue(result.isCompletedExceptionally()); + result.whenComplete((unused, thrown) -> assertSame(except, thrown)); + } + + private void verifyOutcomes(int expected, PolicyResult... results) throws Exception { + List<CompletableFuture<OperationOutcome>> tasks = new LinkedList<>(); + + + OperationOutcome expectedOutcome = null; + + for (int count = 0; count < results.length; ++count) { + OperationOutcome outcome = params.makeOutcome(); + outcome.setResult(results[count]); + tasks.add(CompletableFuture.completedFuture(outcome)); + + if (count == expected) { + expectedOutcome = outcome; + } + } + + CompletableFuture<OperationOutcome> result = oper.allOf(params, tasks); + + assertTrue(executor.runAll()); + assertTrue(result.isDone()); + assertSame(expectedOutcome, result.get()); + } + + private Function<OperationOutcome, CompletableFuture<OperationOutcome>> makeTask( + final OperationOutcome taskOutcome) { + + return outcome -> CompletableFuture.completedFuture(taskOutcome); + } + + @Test + public void testDetmPriority() { + assertEquals(1, oper.detmPriority(null)); + + OperationOutcome outcome = params.makeOutcome(); + + Map<PolicyResult, Integer> map = Map.of(PolicyResult.SUCCESS, 0, PolicyResult.FAILURE_GUARD, 2, + PolicyResult.FAILURE_RETRIES, 3, PolicyResult.FAILURE, 4, PolicyResult.FAILURE_TIMEOUT, 5, + PolicyResult.FAILURE_EXCEPTION, 6); + + for (Entry<PolicyResult, Integer> ent : map.entrySet()) { + outcome.setResult(ent.getKey()); + assertEquals(ent.getKey().toString(), ent.getValue().intValue(), oper.detmPriority(outcome)); + } + } + + /** + * Tests doTask(Future) when the controller is not running. + */ + @Test + public void testDoTaskFutureNotRunning() throws Exception { + CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + controller.complete(params.makeOutcome()); + + CompletableFuture<OperationOutcome> future = + oper.doTask(params, controller, false, params.makeOutcome(), taskFuture); + assertFalse(future.isDone()); + assertTrue(executor.runAll()); + + // should not have run the task + assertFalse(future.isDone()); + + // should have canceled the task future + assertTrue(taskFuture.isCancelled()); + } + + /** + * Tests doTask(Future) when the previous outcome was successful. + */ + @Test + public void testDoTaskFutureSuccess() throws Exception { + CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); + final OperationOutcome taskOutcome = params.makeOutcome(); + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> future = + oper.doTask(params, controller, true, params.makeOutcome(), taskFuture); + + taskFuture.complete(taskOutcome); + assertTrue(executor.runAll()); + + assertTrue(future.isDone()); + assertSame(taskOutcome, future.get()); + + // controller should not be done yet + assertFalse(controller.isDone()); + } + + /** + * Tests doTask(Future) when the previous outcome was failed. + */ + @Test + public void testDoTaskFutureFailure() throws Exception { + CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); + final OperationOutcome failedOutcome = params.makeOutcome(); + failedOutcome.setResult(PolicyResult.FAILURE); + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, failedOutcome, taskFuture); + assertFalse(future.isDone()); + assertTrue(executor.runAll()); + + // should not have run the task + assertFalse(future.isDone()); + + // should have canceled the task future + assertTrue(taskFuture.isCancelled()); + + // controller SHOULD be done now + assertTrue(controller.isDone()); + assertSame(failedOutcome, controller.get()); + } + + /** + * Tests doTask(Future) when the previous outcome was failed, but not checking + * success. + */ + @Test + public void testDoTaskFutureUncheckedFailure() throws Exception { + CompletableFuture<OperationOutcome> taskFuture = new CompletableFuture<>(); + final OperationOutcome failedOutcome = params.makeOutcome(); + failedOutcome.setResult(PolicyResult.FAILURE); + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, failedOutcome, taskFuture); + assertFalse(future.isDone()); + + // complete the task + OperationOutcome taskOutcome = params.makeOutcome(); + taskFuture.complete(taskOutcome); + + assertTrue(executor.runAll()); + + // should have run the task + assertTrue(future.isDone()); + + assertTrue(future.isDone()); + assertSame(taskOutcome, future.get()); + + // controller should not be done yet + assertFalse(controller.isDone()); + } + + /** + * Tests doTask(Function) when the controller is not running. + */ + @Test + public void testDoTaskFunctionNotRunning() throws Exception { + AtomicBoolean invoked = new AtomicBoolean(); + + Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> { + invoked.set(true); + return CompletableFuture.completedFuture(params.makeOutcome()); + }; + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + controller.complete(params.makeOutcome()); + + CompletableFuture<OperationOutcome> future = + oper.doTask(params, controller, false, task).apply(params.makeOutcome()); + assertFalse(future.isDone()); + assertTrue(executor.runAll()); + + // should not have run the task + assertFalse(future.isDone()); + + // should not have even invoked the task + assertFalse(invoked.get()); + } + + /** + * Tests doTask(Function) when the previous outcome was successful. + */ + @Test + public void testDoTaskFunctionSuccess() throws Exception { + final OperationOutcome taskOutcome = params.makeOutcome(); + + final OperationOutcome failedOutcome = params.makeOutcome(); + + Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome); + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome); + + assertTrue(future.isDone()); + assertSame(taskOutcome, future.get()); + + // controller should not be done yet + assertFalse(controller.isDone()); + } + + /** + * Tests doTask(Function) when the previous outcome was failed. + */ + @Test + public void testDoTaskFunctionFailure() throws Exception { + final OperationOutcome failedOutcome = params.makeOutcome(); + failedOutcome.setResult(PolicyResult.FAILURE); + + AtomicBoolean invoked = new AtomicBoolean(); + + Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = outcome -> { + invoked.set(true); + return CompletableFuture.completedFuture(params.makeOutcome()); + }; + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, true, task).apply(failedOutcome); + assertFalse(future.isDone()); + assertTrue(executor.runAll()); + + // should not have run the task + assertFalse(future.isDone()); + + // should not have even invoked the task + assertFalse(invoked.get()); + + // controller should have the failed task + assertTrue(controller.isDone()); + assertSame(failedOutcome, controller.get()); + } + + /** + * Tests doTask(Function) when the previous outcome was failed, but not checking + * success. + */ + @Test + public void testDoTaskFunctionUncheckedFailure() throws Exception { + final OperationOutcome taskOutcome = params.makeOutcome(); + + final OperationOutcome failedOutcome = params.makeOutcome(); + failedOutcome.setResult(PolicyResult.FAILURE); + + Function<OperationOutcome, CompletableFuture<OperationOutcome>> task = makeTask(taskOutcome); + + PipelineControllerFuture<OperationOutcome> controller = new PipelineControllerFuture<>(); + + CompletableFuture<OperationOutcome> future = oper.doTask(params, controller, false, task).apply(failedOutcome); + + assertTrue(future.isDone()); + assertSame(taskOutcome, future.get()); + + // controller should not be done yet + assertFalse(controller.isDone()); + } + + /** + * Tests callbackStarted() when the pipeline has already been stopped. + */ + @Test + public void testCallbackStartedNotRunning() { + AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>(); + + /* + * arrange to stop the controller when the start-callback is invoked, but capture + * the outcome + */ + params = params.toBuilder().startCallback(oper -> { + starter(oper); + future.get().cancel(false); + }).build(); + + future.set(oper.startOperation(params)); + assertTrue(executor.runAll()); + + // should have only run once + assertEquals(1, numStart); + } + + /** + * Tests callbackCompleted() when the pipeline has already been stopped. + */ + @Test + public void testCallbackCompletedNotRunning() { + AtomicReference<Future<OperationOutcome>> future = new AtomicReference<>(); + + // arrange to stop the controller when the start-callback is invoked + params = params.toBuilder().startCallback(oper -> { + future.get().cancel(false); + }).build(); + + future.set(oper.startOperation(params)); + assertTrue(executor.runAll()); + + // should not have been set + assertNull(opend); + assertEquals(0, numEnd); + } + + @Test + public void testSetOutcomeControlLoopOperationOutcomeThrowable() { + final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION)); + + OperationOutcome outcome; + + outcome = new OperationOutcome(); + oper.setOutcome(params, outcome, timex); + assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage()); + assertEquals(PolicyResult.FAILURE_TIMEOUT, outcome.getResult()); + + outcome = new OperationOutcome(); + oper.setOutcome(params, outcome, new IllegalStateException(EXPECTED_EXCEPTION)); + assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage()); + assertEquals(PolicyResult.FAILURE_EXCEPTION, outcome.getResult()); + } + + @Test + public void testSetOutcomeControlLoopOperationOutcomePolicyResult() { + OperationOutcome outcome; + + outcome = new OperationOutcome(); + oper.setOutcome(params, outcome, PolicyResult.SUCCESS); + assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage()); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + + for (PolicyResult result : FAILURE_RESULTS) { + outcome = new OperationOutcome(); + oper.setOutcome(params, outcome, result); + assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage()); + assertEquals(result.toString(), result, outcome.getResult()); + } + } + + @Test + public void testIsTimeout() { + final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION); + + assertFalse(oper.isTimeout(new IllegalStateException(EXPECTED_EXCEPTION))); + assertFalse(oper.isTimeout(new IllegalStateException(timex))); + assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex)))); + assertFalse(oper.isTimeout(new CompletionException(null))); + assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex)))); + + assertTrue(oper.isTimeout(timex)); + assertTrue(oper.isTimeout(new CompletionException(timex))); + } + + @Test + public void testGetTimeOutMillis() { + assertEquals(TIMEOUT * 1000, oper.getTimeOutMillis(params.getTimeoutSec())); + + params = params.toBuilder().timeoutSec(null).build(); + assertEquals(0, oper.getTimeOutMillis(params.getTimeoutSec())); + } + + private void starter(OperationOutcome oper) { + ++numStart; + tstart = oper.getStart(); + opstart = oper; + } + + private void completer(OperationOutcome oper) { + ++numEnd; + opend = oper; + } + + /** + * Gets a function that does nothing. + * + * @param <T> type of input parameter expected by the function + * @return a function that does nothing + */ + private <T> Consumer<T> noop() { + return unused -> { + }; + } + + private OperationOutcome makeSuccess() { + OperationOutcome outcome = params.makeOutcome(); + outcome.setResult(PolicyResult.SUCCESS); + + return outcome; + } + + private OperationOutcome makeFailure() { + OperationOutcome outcome = params.makeOutcome(); + outcome.setResult(PolicyResult.FAILURE); + + return outcome; + } + + /** + * Verifies a run. + * + * @param testName test name + * @param expectedCallbacks number of callbacks expected + * @param expectedOperations number of operation invocations expected + * @param expectedResult expected outcome + */ + private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, + PolicyResult expectedResult) { + + String expectedSubRequestId = + (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations)); + + verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop()); + } + + /** + * Verifies a run. + * + * @param testName test name + * @param expectedCallbacks number of callbacks expected + * @param expectedOperations number of operation invocations expected + * @param expectedResult expected outcome + * @param expectedSubRequestId expected sub request ID + * @param manipulator function to modify the future returned by + * {@link OperatorPartial#startOperation(ControlLoopOperationParams)} before + * the tasks in the executor are run + */ + private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult, + String expectedSubRequestId, Consumer<CompletableFuture<OperationOutcome>> manipulator) { + + CompletableFuture<OperationOutcome> future = oper.startOperation(params); + + manipulator.accept(future); + + assertTrue(testName, executor.runAll()); + + assertEquals(testName, expectedCallbacks, numStart); + assertEquals(testName, expectedCallbacks, numEnd); + + if (expectedCallbacks > 0) { + assertNotNull(testName, opstart); + assertNotNull(testName, opend); + assertEquals(testName, expectedResult, opend.getResult()); + + assertSame(testName, tstart, opstart.getStart()); + assertSame(testName, tstart, opend.getStart()); + + try { + assertTrue(future.isDone()); + assertSame(testName, opend, future.get()); + + } catch (InterruptedException | ExecutionException e) { + throw new IllegalStateException(e); + } + + if (expectedOperations > 0) { + assertEquals(testName, expectedSubRequestId, opend.getSubRequestId()); + } + } + + assertEquals(testName, expectedOperations, oper.getCount()); + } + + private class MyOper extends OperatorPartial { + @Getter + private int count = 0; + + @Setter + private boolean genException; + + @Setter + private int maxFailures = 0; + + @Setter + private CompletableFuture<OperationOutcome> preProcessor; + + public MyOper() { + super(ACTOR, OPERATOR); + } + + @Override + protected OperationOutcome doOperation(ControlLoopOperationParams params, int attempt, + OperationOutcome operation) { + ++count; + if (genException) { + throw new IllegalStateException(EXPECTED_EXCEPTION); + } + + operation.setSubRequestId(String.valueOf(attempt)); + + if (count > maxFailures) { + operation.setResult(PolicyResult.SUCCESS); + } else { + operation.setResult(PolicyResult.FAILURE); + } + + return operation; + } + + @Override + protected CompletableFuture<OperationOutcome> startPreprocessorAsync(ControlLoopOperationParams params) { + return (preProcessor != null ? preProcessor : super.startPreprocessorAsync(params)); + } + + @Override + protected Executor getBlockingExecutor() { + return executor; + } + } + + /** + * Executor that will run tasks until the queue is empty or a maximum number of tasks + * have been executed. + */ + private static class MyExec implements Executor { + private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100; + + private Queue<Runnable> commands = new LinkedList<>(); + + public MyExec() { + // do nothing + } + + public int getQueueLength() { + return commands.size(); + } + + @Override + public void execute(Runnable command) { + commands.add(command); + } + + public boolean runAll() { + for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) { + commands.remove().run(); + } + + return commands.isEmpty(); + } + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java new file mode 100644 index 000000000..7a822c1d9 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java @@ -0,0 +1,212 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.impl; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; + +public class StartConfigPartialTest { + private static final IllegalArgumentException EXPECTED_EXCEPTION = + new IllegalArgumentException("expected exception"); + private static final String MY_NAME = "my-name"; + private static final String PARAMS = "config data"; + private static final String PARAMS2 = "config data #2"; + private static final String PARAMSX = "config data exception"; + + private StartConfigPartial<String> config; + + /** + * Creates a config whose doXxx() methods do nothing. + */ + @Before + public void setUp() { + config = new StartConfigPartial<>(MY_NAME) { + @Override + protected void doConfigure(String parameters) { + // do nothing + } + + @Override + protected void doStart() { + // do nothing + } + + @Override + protected void doStop() { + // do nothing + } + + @Override + protected void doShutdown() { + // do nothing + } + }; + + config = spy(config); + } + + @Test + public void testConfigImpl_testGetFullName() { + assertEquals(MY_NAME, config.getFullName()); + } + + @Test + public void testIsAlive() { + assertFalse(config.isAlive()); + } + + @Test + public void testIsConfigured_testConfigure() { + // throw an exception during doConfigure(), but should remain unconfigured + assertFalse(config.isConfigured()); + doThrow(EXPECTED_EXCEPTION).when(config).doConfigure(PARAMSX); + assertThatIllegalArgumentException().isThrownBy(() -> config.configure(PARAMSX)).isEqualTo(EXPECTED_EXCEPTION); + assertFalse(config.isConfigured()); + + assertFalse(config.isConfigured()); + config.configure(PARAMS); + verify(config).doConfigure(PARAMS); + assertTrue(config.isConfigured()); + + // should not be able to re-configure while running + config.start(); + assertThatIllegalStateException().isThrownBy(() -> config.configure(PARAMS2)).withMessageContaining(MY_NAME); + verify(config, never()).doConfigure(PARAMS2); + + // should be able to re-configure after stopping + config.stop(); + config.configure(PARAMS2); + verify(config).doConfigure(PARAMS2); + assertTrue(config.isConfigured()); + + // should remain configured after exception + doThrow(EXPECTED_EXCEPTION).when(config).doConfigure(PARAMSX); + assertThatIllegalArgumentException().isThrownBy(() -> config.configure(PARAMSX)).isEqualTo(EXPECTED_EXCEPTION); + assertTrue(config.isConfigured()); + } + + @Test + public void testStart() { + assertFalse(config.isAlive()); + + // can't start if not configured yet + assertThatIllegalStateException().isThrownBy(() -> config.start()).withMessageContaining(MY_NAME); + assertFalse(config.isAlive()); + + config.configure(PARAMS); + + config.start(); + verify(config).doStart(); + assertTrue(config.isAlive()); + assertTrue(config.isConfigured()); + + // ok to restart when running, but shouldn't invoke doStart() again + config.start(); + verify(config).doStart(); + assertTrue(config.isAlive()); + assertTrue(config.isConfigured()); + + // should never have invoked these + verify(config, never()).doStop(); + verify(config, never()).doShutdown(); + + // throw exception when started again, but should remain stopped + config.stop(); + doThrow(EXPECTED_EXCEPTION).when(config).doStart(); + assertThatIllegalArgumentException().isThrownBy(() -> config.start()).isEqualTo(EXPECTED_EXCEPTION); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + } + + @Test + public void testStop() { + config.configure(PARAMS); + + // ok to stop if not running, but shouldn't invoke doStop() + config.stop(); + verify(config, never()).doStop(); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + + config.start(); + + // now stop should have an effect + config.stop(); + verify(config).doStop(); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + + // should have only invoked this once + verify(config).doStart(); + + // should never have invoked these + verify(config, never()).doShutdown(); + + // throw exception when stopped again, but should go ahead and stop + config.start(); + doThrow(EXPECTED_EXCEPTION).when(config).doStop(); + assertThatIllegalArgumentException().isThrownBy(() -> config.stop()).isEqualTo(EXPECTED_EXCEPTION); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + } + + @Test + public void testShutdown() { + config.configure(PARAMS); + + // ok to shutdown if not running, but shouldn't invoke doShutdown() + config.shutdown(); + verify(config, never()).doShutdown(); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + + config.start(); + + // now stop should have an effect + config.shutdown(); + verify(config).doShutdown(); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + + // should have only invoked this once + verify(config).doStart(); + + // should never have invoked these + verify(config, never()).doStop(); + + // throw exception when shut down again, but should go ahead and shut down + config.start(); + doThrow(EXPECTED_EXCEPTION).when(config).doShutdown(); + assertThatIllegalArgumentException().isThrownBy(() -> config.shutdown()).isEqualTo(EXPECTED_EXCEPTION); + assertFalse(config.isAlive()); + assertTrue(config.isConfigured()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java new file mode 100644 index 000000000..9dd19d548 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java @@ -0,0 +1,330 @@ +/*- + * ============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.assertThatIllegalArgumentException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.parameters.BeanValidationResult; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Operator; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams.ControlLoopOperationParamsBuilder; +import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.policy.Target; + +public class ControlLoopOperationParamsTest { + private static final String EXPECTED_EXCEPTION = "expected exception"; + private static final String ACTOR = "my-actor"; + private static final String OPERATION = "my-operation"; + private static final Target TARGET = new Target(); + private static final String TARGET_ENTITY = "my-target"; + private static final Integer RETRY = 3; + private static final Integer TIMEOUT = 100; + private static final UUID REQ_ID = UUID.randomUUID(); + + @Mock + private Actor actor; + + @Mock + private ActorService actorService; + + @Mock + private Consumer<OperationOutcome> completer; + + @Mock + private ControlLoopEventContext context; + + @Mock + private VirtualControlLoopEvent event; + + @Mock + private Executor executor; + + @Mock + private CompletableFuture<OperationOutcome> operation; + + @Mock + private Operator operator; + + @Mock + private Consumer<OperationOutcome> starter; + + private Map<String, String> payload; + + private ControlLoopOperationParams params; + private OperationOutcome outcome; + + + /** + * Initializes mocks and sets {@link #params} to a fully-loaded set of parameters. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(actorService.getActor(ACTOR)).thenReturn(actor); + when(actor.getOperator(OPERATION)).thenReturn(operator); + when(operator.startOperation(any())).thenReturn(operation); + + when(event.getRequestId()).thenReturn(REQ_ID); + + when(context.getEvent()).thenReturn(event); + + payload = new TreeMap<>(); + + params = ControlLoopOperationParams.builder().actorService(actorService).completeCallback(completer) + .context(context).executor(executor).actor(ACTOR).operation(OPERATION).payload(payload) + .retry(RETRY).target(TARGET).targetEntity(TARGET_ENTITY).timeoutSec(TIMEOUT) + .startCallback(starter).build(); + + outcome = params.makeOutcome(); + } + + @Test + public void testStart() { + assertSame(operation, params.start()); + + assertThatIllegalArgumentException().isThrownBy(() -> params.toBuilder().context(null).build().start()); + } + + @Test + public void testGetRequestId() { + assertSame(REQ_ID, params.getRequestId()); + + // try with null context + assertNull(params.toBuilder().context(null).build().getRequestId()); + + // try with null event + when(context.getEvent()).thenReturn(null); + assertNull(params.getRequestId()); + } + + @Test + public void testMakeOutcome() { + assertEquals(ACTOR, outcome.getActor()); + assertEquals(OPERATION, outcome.getOperation()); + checkRemainingFields("with actor"); + } + + protected void checkRemainingFields(String testName) { + assertEquals(testName, TARGET_ENTITY, outcome.getTarget()); + assertNull(testName, outcome.getStart()); + assertNull(testName, outcome.getEnd()); + assertNull(testName, outcome.getSubRequestId()); + assertNotNull(testName, outcome.getResult()); + assertNull(testName, outcome.getMessage()); + } + + @Test + public void testCallbackStarted() { + params.callbackStarted(outcome); + verify(starter).accept(outcome); + + // modify starter to throw an exception + AtomicInteger count = new AtomicInteger(); + doAnswer(args -> { + count.incrementAndGet(); + throw new IllegalStateException(EXPECTED_EXCEPTION); + }).when(starter).accept(outcome); + + params.callbackStarted(outcome); + verify(starter, times(2)).accept(outcome); + assertEquals(1, count.get()); + + // repeat with no start-callback - no additional calls expected + params.toBuilder().startCallback(null).build().callbackStarted(outcome); + verify(starter, times(2)).accept(outcome); + assertEquals(1, count.get()); + + // should not call complete-callback + verify(completer, never()).accept(any()); + } + + @Test + public void testCallbackCompleted() { + params.callbackCompleted(outcome); + verify(completer).accept(outcome); + + // modify completer to throw an exception + AtomicInteger count = new AtomicInteger(); + doAnswer(args -> { + count.incrementAndGet(); + throw new IllegalStateException(EXPECTED_EXCEPTION); + }).when(completer).accept(outcome); + + params.callbackCompleted(outcome); + verify(completer, times(2)).accept(outcome); + assertEquals(1, count.get()); + + // repeat with no complete-callback - no additional calls expected + params.toBuilder().completeCallback(null).build().callbackCompleted(outcome); + verify(completer, times(2)).accept(outcome); + assertEquals(1, count.get()); + + // should not call start-callback + verify(starter, never()).accept(any()); + } + + @Test + public void testValidateFields() { + testValidate("actor", "null", bldr -> bldr.actor(null)); + testValidate("actorService", "null", bldr -> bldr.actorService(null)); + testValidate("context", "null", bldr -> bldr.context(null)); + testValidate("executor", "null", bldr -> bldr.executor(null)); + testValidate("operation", "null", bldr -> bldr.operation(null)); + testValidate("target", "null", bldr -> bldr.targetEntity(null)); + + // check edge cases + assertTrue(params.toBuilder().build().validate().isValid()); + + // these can be null + assertTrue(params.toBuilder().payload(null).retry(null).target(null).timeoutSec(null).startCallback(null) + .completeCallback(null).build().validate().isValid()); + + // test with minimal fields + assertTrue(ControlLoopOperationParams.builder().actorService(actorService).context(context).actor(ACTOR) + .operation(OPERATION).targetEntity(TARGET_ENTITY).build().validate().isValid()); + } + + private void testValidate(String fieldName, String expected, + Function<ControlLoopOperationParamsBuilder, ControlLoopOperationParamsBuilder> makeInvalid) { + + // original params should be valid + BeanValidationResult result = params.validate(); + assertTrue(fieldName, result.isValid()); + + // make invalid params + result = makeInvalid.apply(params.toBuilder()).build().validate(); + assertFalse(fieldName, result.isValid()); + assertThat(result.getResult()).contains(fieldName).contains(expected); + } + + @Test + public void testBuilder_testToBuilder() { + assertEquals(params, params.toBuilder().build()); + } + + @Test + public void testGetActor() { + assertSame(ACTOR, params.getActor()); + } + + @Test + public void testGetActorService() { + assertSame(actorService, params.getActorService()); + } + + @Test + public void testGetContext() { + assertSame(context, params.getContext()); + } + + @Test + public void testGetExecutor() { + assertSame(executor, params.getExecutor()); + + // should use default when unspecified + assertSame(ForkJoinPool.commonPool(), ControlLoopOperationParams.builder().build().getExecutor()); + } + + @Test + public void testGetOperation() { + assertSame(OPERATION, params.getOperation()); + } + + @Test + public void testGetPayload() { + assertSame(payload, params.getPayload()); + + // should be null when unspecified + assertNull(ControlLoopOperationParams.builder().build().getPayload()); + } + + @Test + public void testGetRetry() { + assertSame(RETRY, params.getRetry()); + + // should be null when unspecified + assertNull(ControlLoopOperationParams.builder().build().getRetry()); + } + + @Test + public void testTarget() { + assertSame(TARGET, params.getTarget()); + + // should be null when unspecified + assertNull(ControlLoopOperationParams.builder().build().getTarget()); + } + + @Test + public void testGetTimeoutSec() { + assertSame(TIMEOUT, params.getTimeoutSec()); + + // should be 300 when unspecified + assertEquals(Integer.valueOf(300), ControlLoopOperationParams.builder().build().getTimeoutSec()); + + // null should be ok too + assertNull(ControlLoopOperationParams.builder().timeoutSec(null).build().getTimeoutSec()); + } + + @Test + public void testGetStartCallback() { + assertSame(starter, params.getStartCallback()); + } + + @Test + public void testGetCompleteCallback() { + assertSame(completer, params.getCompleteCallback()); + } + + @Test + public void testGetTargetEntity() { + assertEquals(TARGET_ENTITY, params.getTargetEntity()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java new file mode 100644 index 000000000..6c1f538ec --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java @@ -0,0 +1,132 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ValidationResult; + +public class HttpActorParamsTest { + + private static final String CONTAINER = "my-container"; + private static final String CLIENT = "my-client"; + private static final long TIMEOUT = 10; + + private static final String PATH1 = "path #1"; + private static final String PATH2 = "path #2"; + private static final String URI1 = "uri #1"; + private static final String URI2 = "uri #2"; + + private Map<String, String> paths; + private HttpActorParams params; + + /** + * Initializes {@link #paths} with two items and {@link params} with a fully populated + * object. + */ + @Before + public void setUp() { + paths = new TreeMap<>(); + paths.put(PATH1, URI1); + paths.put(PATH2, URI2); + + params = makeHttpActorParams(); + } + + @Test + public void testMakeOperationParameters() { + Function<String, Map<String, Object>> maker = params.makeOperationParameters(CONTAINER); + assertNull(maker.apply("unknown-operation")); + + Map<String, Object> subparam = maker.apply(PATH1); + assertNotNull(subparam); + assertEquals("{clientName=my-client, path=uri #1, timeoutSec=10}", new TreeMap<>(subparam).toString()); + + subparam = maker.apply(PATH2); + assertNotNull(subparam); + assertEquals("{clientName=my-client, path=uri #2, timeoutSec=10}", new TreeMap<>(subparam).toString()); + } + + @Test + public void testDoValidation() { + assertThatCode(() -> params.doValidation(CONTAINER)).doesNotThrowAnyException(); + + // invalid param + params.setClientName(null); + assertThatThrownBy(() -> params.doValidation(CONTAINER)) + .isInstanceOf(ParameterValidationRuntimeException.class); + } + + @Test + public void testValidate() { + assertTrue(params.validate(CONTAINER).isValid()); + + testValidateField("clientName", "null", params2 -> params2.setClientName(null)); + testValidateField("path", "null", params2 -> params2.setPath(null)); + testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1)); + + // check edge cases + params.setTimeoutSec(0); + assertTrue(params.validate(CONTAINER).isValid()); + + params.setTimeoutSec(1); + assertTrue(params.validate(CONTAINER).isValid()); + + // one path value is null + testValidateField(PATH2, "null", params2 -> paths.put(PATH2, null)); + } + + private void testValidateField(String fieldName, String expected, Consumer<HttpActorParams> makeInvalid) { + + // original params should be valid + ValidationResult result = params.validate(CONTAINER); + assertTrue(fieldName, result.isValid()); + + // make invalid params + HttpActorParams params2 = makeHttpActorParams(); + makeInvalid.accept(params2); + result = params2.validate(CONTAINER); + assertFalse(fieldName, result.isValid()); + assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected); + } + + private HttpActorParams makeHttpActorParams() { + HttpActorParams params2 = new HttpActorParams(); + params2.setClientName(CLIENT); + params2.setTimeoutSec(TIMEOUT); + params2.setPath(paths); + + return params2; + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java new file mode 100644 index 000000000..6cf7328ca --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java @@ -0,0 +1,82 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams.HttpParamsBuilder; + +public class HttpParamsTest { + + private static final String CONTAINER = "my-container"; + private static final String CLIENT = "my-client"; + private static final String PATH = "my-path"; + private static final long TIMEOUT = 10; + + private HttpParams params; + + @Before + public void setUp() { + params = HttpParams.builder().clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build(); + } + + @Test + public void testValidate() { + assertTrue(params.validate(CONTAINER).isValid()); + + testValidateField("clientName", "null", bldr -> bldr.clientName(null)); + testValidateField("path", "null", bldr -> bldr.path(null)); + testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1)); + + // check edge cases + assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid()); + assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid()); + } + + @Test + public void testBuilder_testToBuilder() { + assertEquals(CLIENT, params.getClientName()); + assertEquals(PATH, params.getPath()); + assertEquals(TIMEOUT, params.getTimeoutSec()); + + assertEquals(params, params.toBuilder().build()); + } + + private void testValidateField(String fieldName, String expected, + Function<HttpParamsBuilder, HttpParamsBuilder> makeInvalid) { + + // original params should be valid + ValidationResult result = params.validate(CONTAINER); + assertTrue(fieldName, result.isValid()); + + // make invalid params + result = makeInvalid.apply(params.toBuilder()).build().validate(CONTAINER); + assertFalse(fieldName, result.isValid()); + assertThat(result.getResult()).contains(fieldName).contains(expected); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java new file mode 100644 index 000000000..9879f604f --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java @@ -0,0 +1,82 @@ +/*- + * ============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.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ObjectValidationResult; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.parameters.ValidationStatus; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException; + +public class ParameterValidationRuntimeExceptionTest { + + private static final String THE_MESSAGE = "the message"; + private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception"); + + private ValidationResult result; + + @Before + public void setUp() { + result = new ObjectValidationResult("param", null, ValidationStatus.INVALID, "null"); + } + + @Test + public void testParameterValidationExceptionValidationResult() { + ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(result); + assertSame(result, ex.getResult()); + assertNull(ex.getMessage()); + } + + @Test + public void testParameterValidationExceptionValidationResultString() { + ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(THE_MESSAGE, result); + assertSame(result, ex.getResult()); + assertEquals(THE_MESSAGE, ex.getMessage()); + } + + @Test + public void testParameterValidationExceptionValidationResultThrowable() { + ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(EXPECTED_EXCEPTION, result); + assertSame(result, ex.getResult()); + assertEquals(EXPECTED_EXCEPTION.toString(), ex.getMessage()); + assertEquals(EXPECTED_EXCEPTION, ex.getCause()); + } + + @Test + public void testParameterValidationExceptionValidationResultStringThrowable() { + ParameterValidationRuntimeException ex = + new ParameterValidationRuntimeException(THE_MESSAGE, EXPECTED_EXCEPTION, result); + assertSame(result, ex.getResult()); + assertEquals(THE_MESSAGE, ex.getMessage()); + assertEquals(EXPECTED_EXCEPTION, ex.getCause()); + } + + @Test + public void testGetResult() { + ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(result); + assertSame(result, ex.getResult()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java new file mode 100644 index 000000000..4834c98d2 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java @@ -0,0 +1,80 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.actorserviceprovider.parameters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicParams.TopicParamsBuilder; + +public class TopicParamsTest { + + 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 TopicParams params; + + @Before + public void setUp() { + params = TopicParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build(); + } + + @Test + public void testValidate() { + testValidateField("target", "null", bldr -> bldr.target(null)); + testValidateField("source", "null", bldr -> bldr.source(null)); + testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1)); + + // check edge cases + assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid()); + assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid()); + } + + @Test + public void testBuilder_testToBuilder() { + assertEquals(TARGET, params.getTarget()); + assertEquals(SOURCE, params.getSource()); + assertEquals(TIMEOUT, params.getTimeoutSec()); + + assertEquals(params, params.toBuilder().build()); + } + + private void testValidateField(String fieldName, String expected, + Function<TopicParamsBuilder, TopicParamsBuilder> makeInvalid) { + + // original params should be valid + ValidationResult result = params.validate(CONTAINER); + assertTrue(fieldName, result.isValid()); + + // make invalid params + result = makeInvalid.apply(params.toBuilder()).build().validate(CONTAINER); + assertFalse(fieldName, result.isValid()); + assertThat(result.getResult()).contains(fieldName).contains(expected); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java new file mode 100644 index 000000000..de1cf0f8d --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java @@ -0,0 +1,142 @@ +/*- + * ============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.pipeline; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.concurrent.Future; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class FutureManagerTest { + + private static final String EXPECTED_EXCEPTION = "expected exception"; + + @Mock + private Future<String> future1; + + @Mock + private Future<String> future2; + + @Mock + private Future<String> future3; + + private FutureManager mgr; + + /** + * Initializes fields, including {@link #mgr}. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mgr = new FutureManager(); + } + + @Test + public void testStop() { + mgr.add(future1); + mgr.add(future2); + mgr.add(future3); + + // arrange for one to throw an exception + when(future2.cancel(anyBoolean())).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION)); + + // nothing should have been canceled yet + verify(future1, never()).cancel(anyBoolean()); + verify(future2, never()).cancel(anyBoolean()); + verify(future3, never()).cancel(anyBoolean()); + + assertTrue(mgr.isRunning()); + + // stop the controller + + // stop the controller + mgr.stop(); + + // all controllers should now be stopped + assertFalse(mgr.isRunning()); + + // everything should have been invoked + verify(future1).cancel(anyBoolean()); + verify(future2).cancel(anyBoolean()); + verify(future3).cancel(anyBoolean()); + + // re-invoking stop should have no effect on the listeners + mgr.stop(); + + verify(future1).cancel(anyBoolean()); + verify(future2).cancel(anyBoolean()); + verify(future3).cancel(anyBoolean()); + } + + @Test + public void testAdd() { + // still running - this should not be invoked + mgr.add(future1); + verify(future1, never()).cancel(anyBoolean()); + + // re-add should have no impact + mgr.add(future1); + verify(future1, never()).cancel(anyBoolean()); + + mgr.stop(); + + verify(future1).cancel(anyBoolean()); + + // new additions should be invoked immediately + mgr.add(future2); + verify(future2).cancel(anyBoolean()); + + // should work with exceptions, too + when(future3.cancel(anyBoolean())).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION)); + mgr.add(future3); + } + + @Test + public void testRemove() { + mgr.add(future1); + mgr.add(future2); + + verify(future1, never()).cancel(anyBoolean()); + verify(future2, never()).cancel(anyBoolean()); + + // remove the second + mgr.remove(future2); + + // should be able to remove it again + mgr.remove(future2); + + mgr.stop(); + + // first should have run, but not the second + verify(future1).cancel(anyBoolean()); + + verify(future2, never()).cancel(anyBoolean()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java new file mode 100644 index 000000000..4a882d422 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java @@ -0,0 +1,134 @@ +/*- + * ============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.pipeline; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ListenerManagerTest { + + private static final String EXPECTED_EXCEPTION = "expected exception"; + + @Mock + private Runnable runnable1; + + @Mock + private Runnable runnable2; + + @Mock + private Runnable runnable3; + + private ListenerManager mgr; + + /** + * Initializes fields, including {@link #mgr}. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mgr = new ListenerManager(); + } + + @Test + public void testStop_testIsRunning() { + mgr.add(runnable1); + mgr.add(runnable2); + mgr.add(runnable3); + + // arrange for one to throw an exception + doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(runnable2).run(); + + // nothing should have been canceled yet + verify(runnable1, never()).run(); + verify(runnable2, never()).run(); + verify(runnable3, never()).run(); + + assertTrue(mgr.isRunning()); + + // stop the controller + mgr.stop(); + + // all controllers should now be stopped + assertFalse(mgr.isRunning()); + + // everything should have been invoked + verify(runnable1).run(); + verify(runnable2).run(); + verify(runnable3).run(); + + // re-invoking stop should have no effect on the listeners + mgr.stop(); + + verify(runnable1).run(); + verify(runnable2).run(); + verify(runnable3).run(); + } + + @Test + public void testAdd() { + // still running - this should not be invoked + mgr.add(runnable1); + verify(runnable1, never()).run(); + + mgr.stop(); + + verify(runnable1).run(); + + // new additions should be invoked immediately + mgr.add(runnable2); + verify(runnable2).run(); + + // should work with exceptions, too + doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(runnable3).run(); + mgr.add(runnable3); + } + + @Test + public void testRemove() { + mgr.add(runnable1); + mgr.add(runnable2); + + verify(runnable1, never()).run(); + verify(runnable2, never()).run(); + + // remove the second + mgr.remove(runnable2); + + // should be able to remove it again + mgr.remove(runnable2); + + mgr.stop(); + + // first should have run, but not the second + verify(runnable1).run(); + + verify(runnable2, never()).run(); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java new file mode 100644 index 000000000..a6b11ef65 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java @@ -0,0 +1,471 @@ +/*- + * ============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.pipeline; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Function; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class PipelineControllerFutureTest { + private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception"); + private static final String TEXT = "some text"; + + @Mock + private Runnable runnable1; + + @Mock + private Runnable runnable2; + + @Mock + private Future<String> future1; + + @Mock + private Future<String> future2; + + @Mock + private Executor executor; + + + private CompletableFuture<String> compFuture; + private PipelineControllerFuture<String> controller; + + + /** + * Initializes fields, including {@link #controller}. Adds all runners and futures to + * the controller. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + compFuture = spy(new CompletableFuture<>()); + + controller = new PipelineControllerFuture<>(); + + controller.add(runnable1); + controller.add(future1); + controller.add(runnable2); + controller.add(future2); + } + + @Test + public void testCancel_testAddFutureOfFBoolean_testAddRunnable__testIsRunning() { + assertTrue(controller.isRunning()); + + assertTrue(controller.cancel(false)); + + assertTrue(controller.isCancelled()); + assertFalse(controller.isRunning()); + + verifyStopped(); + + // re-invoke; nothing should change + assertTrue(controller.cancel(true)); + + assertTrue(controller.isCancelled()); + assertFalse(controller.isRunning()); + + verifyStopped(); + } + + @Test + public void testCompleteT() throws Exception { + assertTrue(controller.complete(TEXT)); + assertEquals(TEXT, controller.get()); + + verifyStopped(); + + // repeat - disallowed + assertFalse(controller.complete(TEXT)); + } + + @Test + public void testCompleteExceptionallyThrowable() { + assertTrue(controller.completeExceptionally(EXPECTED_EXCEPTION)); + assertThatThrownBy(() -> controller.get()).hasCause(EXPECTED_EXCEPTION); + + verifyStopped(); + + // repeat - disallowed + assertFalse(controller.completeExceptionally(EXPECTED_EXCEPTION)); + } + + @Test + public void testCompleteAsyncSupplierOfQextendsTExecutor() throws Exception { + CompletableFuture<String> future = controller.completeAsync(() -> TEXT, executor); + + // haven't stopped anything yet + assertFalse(future.isDone()); + verify(runnable1, never()).run(); + + // get the operation and run it + ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); + verify(executor).execute(captor.capture()); + captor.getValue().run(); + + // should be done now + assertTrue(future.isDone()); + + assertEquals(TEXT, future.get()); + + verifyStopped(); + } + + /** + * Tests completeAsync(executor) when canceled before execution. + */ + @Test + public void testCompleteAsyncSupplierOfQextendsTExecutorCanceled() throws Exception { + CompletableFuture<String> future = controller.completeAsync(() -> TEXT, executor); + + assertTrue(future.cancel(false)); + + verifyStopped(); + + assertTrue(future.isDone()); + + assertThatThrownBy(() -> controller.get()).isInstanceOf(CancellationException.class); + } + + @Test + public void testCompleteAsyncSupplierOfQextendsT() throws Exception { + CompletableFuture<String> future = controller.completeAsync(() -> TEXT); + assertEquals(TEXT, future.get()); + + verifyStopped(); + } + + /** + * Tests completeAsync() when canceled. + */ + @Test + public void testCompleteAsyncSupplierOfQextendsTCanceled() throws Exception { + CountDownLatch canceled = new CountDownLatch(1); + + // run async, but await until canceled + CompletableFuture<String> future = controller.completeAsync(() -> { + try { + canceled.await(); + } catch (InterruptedException e) { + // do nothing + } + + return TEXT; + }); + + assertTrue(future.cancel(false)); + + // let the future run now + canceled.countDown(); + + verifyStopped(); + + assertTrue(future.isDone()); + + assertThatThrownBy(() -> controller.get()).isInstanceOf(CancellationException.class); + } + + @Test + public void testCompleteOnTimeoutTLongTimeUnit() throws Exception { + CountDownLatch stopped = new CountDownLatch(1); + controller.add(() -> stopped.countDown()); + + CompletableFuture<String> future = controller.completeOnTimeout(TEXT, 1, TimeUnit.MILLISECONDS); + + assertEquals(TEXT, future.get()); + + /* + * Must use latch instead of verifyStopped(), because the runnables may be + * executed asynchronously. + */ + assertTrue(stopped.await(5, TimeUnit.SECONDS)); + } + + /** + * Tests completeOnTimeout() when completed before the timeout. + */ + @Test + public void testCompleteOnTimeoutTLongTimeUnitNoTimeout() throws Exception { + CompletableFuture<String> future = controller.completeOnTimeout("timed out", 5, TimeUnit.SECONDS); + controller.complete(TEXT); + + assertEquals(TEXT, future.get()); + + verifyStopped(); + } + + /** + * Tests completeOnTimeout() when canceled before the timeout. + */ + @Test + public void testCompleteOnTimeoutTLongTimeUnitCanceled() { + CompletableFuture<String> future = controller.completeOnTimeout(TEXT, 5, TimeUnit.SECONDS); + assertTrue(future.cancel(true)); + + assertThatThrownBy(() -> controller.get()).isInstanceOf(CancellationException.class); + + verifyStopped(); + } + + @Test + public void testNewIncompleteFuture() { + PipelineControllerFuture<String> future = controller.newIncompleteFuture(); + assertNotNull(future); + assertTrue(future instanceof PipelineControllerFuture); + assertNotSame(controller, future); + assertFalse(future.isDone()); + } + + @Test + public void testDelayedComplete() throws Exception { + controller.add(runnable1); + + BiConsumer<String, Throwable> stopper = controller.delayedComplete(); + + // shouldn't have run yet + assertTrue(controller.isRunning()); + verify(runnable1, never()).run(); + + stopper.accept(TEXT, null); + + assertTrue(controller.isDone()); + assertEquals(TEXT, controller.get()); + + assertFalse(controller.isRunning()); + verify(runnable1).run(); + + // re-invoke; nothing should change + stopper.accept(TEXT, EXPECTED_EXCEPTION); + assertFalse(controller.isCompletedExceptionally()); + + assertFalse(controller.isRunning()); + verify(runnable1).run(); + } + + /** + * Tests delayedComplete() when an exception is generated. + */ + @Test + public void testDelayedCompleteWithException() throws Exception { + controller.add(runnable1); + + BiConsumer<String, Throwable> stopper = controller.delayedComplete(); + + // shouldn't have run yet + assertTrue(controller.isRunning()); + verify(runnable1, never()).run(); + + stopper.accept(TEXT, EXPECTED_EXCEPTION); + + assertTrue(controller.isDone()); + assertThatThrownBy(() -> controller.get()).hasCause(EXPECTED_EXCEPTION); + + assertFalse(controller.isRunning()); + verify(runnable1).run(); + + // re-invoke; nothing should change + stopper.accept(TEXT, null); + assertTrue(controller.isCompletedExceptionally()); + + assertFalse(controller.isRunning()); + verify(runnable1).run(); + } + + @Test + public void testDelayedRemoveFutureOfF() throws Exception { + BiConsumer<String, Throwable> remover = controller.delayedRemove(future1); + + remover.accept(TEXT, EXPECTED_EXCEPTION); + + // should not have completed the controller + assertFalse(controller.isDone()); + + verify(future1, never()).cancel(anyBoolean()); + + controller.delayedComplete().accept(TEXT, EXPECTED_EXCEPTION); + + verify(future1, never()).cancel(anyBoolean()); + verify(future2).cancel(anyBoolean()); + } + + @Test + public void testDelayedRemoveRunnable() throws Exception { + BiConsumer<String, Throwable> remover = controller.delayedRemove(runnable1); + + remover.accept(TEXT, EXPECTED_EXCEPTION); + + // should not have completed the controller + assertFalse(controller.isDone()); + + verify(runnable1, never()).run(); + + controller.delayedComplete().accept(TEXT, EXPECTED_EXCEPTION); + + verify(runnable1, never()).run(); + verify(runnable2).run(); + } + + @Test + public void testRemoveFutureOfF_testRemoveRunnable() { + controller.remove(runnable2); + controller.remove(future1); + + controller.cancel(true); + + verify(runnable1).run(); + verify(runnable2, never()).run(); + verify(future1, never()).cancel(anyBoolean()); + verify(future2).cancel(anyBoolean()); + } + + /** + * Tests both wrap() methods. + */ + @Test + public void testWrap() throws Exception { + controller = spy(controller); + + CompletableFuture<String> future = controller.wrap(compFuture); + verify(controller, never()).remove(compFuture); + + compFuture.complete(TEXT); + assertEquals(TEXT, future.get()); + + verify(controller).remove(compFuture); + } + + /** + * Tests wrap(), when the controller is not running. + */ + @Test + public void testWrapNotRunning() throws Exception { + controller.cancel(false); + controller = spy(controller); + + assertFalse(controller.wrap(compFuture).isDone()); + verify(controller, never()).add(compFuture); + verify(controller, never()).remove(compFuture); + + verify(compFuture).cancel(anyBoolean()); + } + + /** + * Tests wrap(), when the future throws an exception. + */ + @Test + public void testWrapException() throws Exception { + controller = spy(controller); + + CompletableFuture<String> future = controller.wrap(compFuture); + verify(controller, never()).remove(compFuture); + + compFuture.completeExceptionally(EXPECTED_EXCEPTION); + assertThatThrownBy(() -> future.get()).hasCause(EXPECTED_EXCEPTION); + + verify(controller).remove(compFuture); + } + + @Test + public void testWrapFunction() throws Exception { + + Function<String, CompletableFuture<String>> func = controller.wrap(input -> { + compFuture.complete(input); + return compFuture; + }); + + CompletableFuture<String> future = func.apply(TEXT); + assertTrue(compFuture.isDone()); + + assertEquals(TEXT, future.get()); + + // should not have completed the controller + assertFalse(controller.isDone()); + } + + /** + * Tests add(Function) when the controller is canceled after the future is added. + */ + @Test + public void testWrapFunctionCancel() throws Exception { + Function<String, CompletableFuture<String>> func = controller.wrap(input -> compFuture); + + CompletableFuture<String> future = func.apply(TEXT); + assertFalse(future.isDone()); + + assertFalse(compFuture.isDone()); + + // cancel - should propagate + controller.cancel(false); + + verify(compFuture).cancel(anyBoolean()); + } + + /** + * Tests add(Function) when the controller is not running. + */ + @Test + public void testWrapFunctionNotRunning() { + AtomicReference<String> value = new AtomicReference<>(); + + Function<String, CompletableFuture<String>> func = controller.wrap(input -> { + value.set(input); + return compFuture; + }); + + controller.cancel(false); + + CompletableFuture<String> fut = func.apply(TEXT); + assertNotSame(compFuture, fut); + assertFalse(fut.isDone()); + + assertNull(value.get()); + } + + private void verifyStopped() { + verify(runnable1).run(); + verify(runnable2).run(); + verify(future1).cancel(anyBoolean()); + verify(future2).cancel(anyBoolean()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml new file mode 100644 index 000000000..c7fe46e47 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ============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========================================================= +--> + +<configuration> + + <contextName>Actors</contextName> + <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /> + + <!-- USE FOR STD OUT ONLY --> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <Pattern>%d %level %msg%n</Pattern> + </encoder> + </appender> + + <root level="warn"> + <appender-ref ref="STDOUT" /> + </root> + + <!-- this is required for UtilTest --> + <logger name="org.onap.policy.controlloop.actorserviceprovider.Util" level="info" additivity="false"> + <appender-ref ref="STDOUT" /> + </logger> +</configuration> |