diff options
author | Jim Hahn <jrh3@att.com> | 2020-02-22 17:11:20 -0500 |
---|---|---|
committer | Jim Hahn <jrh3@att.com> | 2020-03-06 12:42:32 -0500 |
commit | c1f79cd311ad62d3adb374921b8c3d303db5add6 (patch) | |
tree | 58babf7d14b280825d0e02e153634a056c3fd724 /controlloop | |
parent | b3a65de5156de7e2f4ac6e69c515576d077443aa (diff) |
Add frankfurt rules for Actor redesign
Note: VcpeTest and VfwTest are not included, as they depend on updates
to the APPC and APPC-LCM Actors.
Added feature-controlloop-frankfurt.
Added HTTP client property files to feature-controlloop-management.
Updates per review comments:
- pom changes
- simplify FrankfurtBase
- rename event-svc-http.properties
- change "usescases" to "frankfurt"
- use blanks for CDS property defaults
- trailing spaces in http-client files
- add https property to http-client files
Added newlines to config files that appear to be missing them (based
on feedback from gerrit).
Issue-ID: POLICY-2385
Signed-off-by: Jim Hahn <jrh3@att.com>
Change-Id: Ib4a4d75461c734ae47309e41dc9d099e8815d55d
Diffstat (limited to 'controlloop')
66 files changed, 7857 insertions, 13 deletions
diff --git a/controlloop/common/controller-frankfurt/pom.xml b/controlloop/common/controller-frankfurt/pom.xml new file mode 100644 index 000000000..9633f1f30 --- /dev/null +++ b/controlloop/common/controller-frankfurt/pom.xml @@ -0,0 +1,241 @@ +<?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========================================================= + --> + +<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.drools-applications.controlloop.common</groupId> + <artifactId>drools-applications-common</artifactId> + <version>1.6.0-SNAPSHOT</version> + </parent> + + <artifactId>controller-frankfurt</artifactId> + <packaging>kjar</packaging> + + <name>${project.artifactId}</name> + <description>Frankfurt Experimental Controller</description> + + <build> + <plugins> + <plugin> + <groupId>org.kie</groupId> + <artifactId>kie-maven-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>events</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>aai</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>appc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>appclcm</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>cds</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>guard</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>sdc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>sdnc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>sdnr</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>so</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId> + <artifactId>vfc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>eventmanager</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actorServiceProvider</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.aai</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.appc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.appclcm</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.cds</artifactId> + <version>${policy.models.version}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.guard</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.sdnc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.sdnr</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.so</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.vfc</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions</groupId> + <artifactId>model-yaml</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-pdp</groupId> + <artifactId>policy-management</artifactId> + <version>${version.policy.drools-pdp}</version> + <scope>provided</scope> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions</groupId> + <artifactId>simulators</artifactId> + <version>${policy.models.version}</version> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <!--This profile is used to store Eclipse m2e settings only. It has no + influence on the Maven build itself. --> + <id>only-eclipse</id> + <activation> + <property> + <name>m2e.version</name> + </property> + </activation> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.eclipse.m2e</groupId> + <artifactId>lifecycle-mapping</artifactId> + <version>1.0.0</version> + <configuration> + <lifecycleMappingMetadata> + <pluginExecutions> + <pluginExecution> + <pluginExecutionFilter> + <groupId>org.kie</groupId> + <artifactId>kie-maven-plugin</artifactId> + <goals> + <goal>build</goal> + </goals> + </pluginExecutionFilter> + <action> + <ignore /> + </action> + </pluginExecution> + </pluginExecutions> + </lifecycleMappingMetadata> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + </profile> + </profiles> +</project> diff --git a/controlloop/common/controller-frankfurt/src/main/resources/META-INF/kmodule.xml b/controlloop/common/controller-frankfurt/src/main/resources/META-INF/kmodule.xml new file mode 100644 index 000000000..b5e4e3f0e --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/main/resources/META-INF/kmodule.xml @@ -0,0 +1,27 @@ +<?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========================================================= + --> +<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"> + <kbase name="onap.policies.controlloop.operational.common.Drools" default="false" equalsBehavior="equality"/> + <kbase name="onap.policies.controlloop.Operational" equalsBehavior="equality" + packages="org.onap.policy.controlloop" includes="onap.policies.controlloop.operational.common.Drools"> + <ksession name="frankfurt"/> + </kbase> +</kmodule> diff --git a/controlloop/common/controller-frankfurt/src/main/resources/frankfurt.drl b/controlloop/common/controller-frankfurt/src/main/resources/frankfurt.drl new file mode 100644 index 000000000..00c4f5f10 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/main/resources/frankfurt.drl @@ -0,0 +1,348 @@ +/* + * ============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; + +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.CanonicalOnset; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2.NewEventStatus; +import org.onap.policy.controlloop.eventmanager.ControlLoopOperationManager2; +import org.onap.policy.controlloop.utils.ControlLoopUtils; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; + +import org.slf4j.LoggerFactory; +import org.slf4j.Logger; + +import org.onap.policy.drools.system.PolicyEngineConstants; + +/* +* +* Called when the ControlLoopParams object has been inserted into working memory from the BRMSGW. +* +*/ +rule "INSERT.PARAMS" + when + $params : ControlLoopParams() + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {} : TOSCA-POLICY=[{}]", $params.getClosedLoopControlName(), $params.getPolicyName() + "." + + drools.getRule().getName(), $params.getToscaPolicy()); +end + +/* +* +* Called when a Tosca Policy is present. +* +*/ +rule "NEW.TOSCA.POLICY" + when + $policy : ToscaPolicy() + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: [{}|{}|{}|{}]: CONTENT: {}", drools.getRule().getName(), + $policy.getType(), $policy.getTypeVersion(), $policy.getName(), + $policy.getVersion(), $policy); + + ControlLoopParams params = ControlLoopUtils.toControlLoopParams($policy); + if (params != null) { + insert(params); + } +end + +/* + * Remove Control Loop Parameters. + */ +rule "REMOVE.PARAMS" + when + $params : ControlLoopParams( $policyName : getPolicyName(), $policyVersion : getPolicyVersion() ) + not ( ToscaPolicy( getName() == $policyName, getVersion() == $policyVersion ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: [{}|{}|{}]", drools.getRule().getName(), + $params.getPolicyScope(), $params.getPolicyName(), $params.getPolicyVersion()); + + retract($params); +end + +/* +* +* This rule responds to DCAE Events where there is no manager yet. Either it is +* the first ONSET, or a subsequent badly formed Event (i.e. Syntax error, or is-closed-loop-disabled) +* +*/ +rule "EVENT" + when + $params : ControlLoopParams( $clName : getClosedLoopControlName() ) + $event : CanonicalOnset( closedLoopControlName == $clName ) + not ( ControlLoopEventManager2( closedLoopControlName == $event.getClosedLoopControlName(), + requestId == $event.getRequestId() ) ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}.{}: event={}", + $clName, $params.getPolicyName(), drools.getRule().getName(), + $event); + // + // Retract the event from memory; it will be managed by the manager for now on + // + retract($event); + + VirtualControlLoopNotification notification; + + try { + // + // Check the event, because we need it to not be null when + // we create the ControlLoopEventManager2. The ControlLoopEventManager2 + // will do extra syntax checking as well as check if the closed loop is disabled. + // + if ($event.getRequestId() == null) { + notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.REJECTED); + notification.setFrom("policy"); + notification.setMessage("Missing requestId"); + notification.setPolicyName($params.getPolicyName() + "." + drools.getRule().getName()); + notification.setPolicyScope($params.getPolicyScope()); + notification.setPolicyVersion($params.getPolicyVersion()); + + } else { + ControlLoopEventManager2 manager = new ControlLoopEventManager2($params, $event, drools.getWorkingMemory()); + insert(manager); + try { + manager.start(); + } catch(Exception e) { + retract(manager); + throw e; + } + notification = manager.makeNotification(); + notification.setNotification(ControlLoopNotificationType.ACTIVE); + notification.setPolicyName($params.getPolicyName() + "." + drools.getRule().getName()); + } + } catch (Exception e) { + logger.warn("{}: {}.{}", $clName, $params.getPolicyName(), drools.getRule().getName(), e); + notification = new VirtualControlLoopNotification($event); + notification.setNotification(ControlLoopNotificationType.REJECTED); + notification.setMessage("Exception occurred: " + e.getMessage()); + notification.setPolicyName($params.getPolicyName() + "." + drools.getRule().getName()); + notification.setPolicyScope($params.getPolicyScope()); + notification.setPolicyVersion($params.getPolicyVersion()); + } + // + // Generate notification + // + try { + PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", notification); + + } catch(RuntimeException e) { + logger.warn("{}: {}.{}: event={} exception generating notification", + $clName, $params.getPolicyName(), drools.getRule().getName(), + $event, e); + } +end + +/* +* +* This rule happens when we get a subsequent event. +* +*/ +rule "EVENT.MANAGER.NEW.EVENT" + when + $event : VirtualControlLoopEvent( ) + $manager : ControlLoopEventManager2( closedLoopControlName == $event.getClosedLoopControlName(), + requestId == $event.getRequestId() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}.{}: event={} manager={}", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName(), + $event, $manager); + // + // Remove the event from memory + // + retract($event); + + // + // Check what kind of event this is + // + switch($manager.onNewEvent($event)) { + case SYNTAX_ERROR: + // + // Ignore any bad syntax events + // + logger.warn("{}: {}.{}: syntax error", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName()); + break; + + case FIRST_ABATEMENT: + case SUBSEQUENT_ABATEMENT: + // + // TODO: handle the abatement. Currently, it's just discarded. + // + break; + + case FIRST_ONSET: + case SUBSEQUENT_ONSET: + default: + // + // We don't care about subsequent onsets + // + logger.warn("{}: {}.{}: subsequent onset", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName()); + break; + } +end + +/* +* +* Step completed +* +*/ +rule "EVENT.MANAGER.PROCESSING" + when + $manager : ControlLoopEventManager2( isUpdated(), isActive(), $notification : getNotification() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}.{}: manager={}", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName(), + $manager); + // + // Generate notification + // + try { + $notification.setPolicyName($manager.getPolicyName() + "." + drools.getRule().getName()); + PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", $notification); + + } catch(RuntimeException e) { + logger.warn("{}: {}.{}: manager={} exception generating notification", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName(), + $manager, e); + } + // + // Discard this message and wait for the next response. + // + $manager.nextStep(); + update($manager); +end + +/* +* +* Final step completed +* +*/ +rule "EVENT.MANAGER.FINAL" + when + $manager : ControlLoopEventManager2( !isActive(), $notification : getNotification() ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}.{}: manager={}", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName(), + $manager); + // + // Generate notification + // + try { + $notification.setPolicyName($manager.getPolicyName() + "." + drools.getRule().getName()); + PolicyEngineConstants.getManager().deliver("POLICY-CL-MGT", $notification); + + } catch(RuntimeException e) { + logger.warn("{}: {}.{}: manager={} exception generating notification", + $manager.getClosedLoopControlName(), $manager.getPolicyName(), drools.getRule().getName(), + $manager, e); + } + // + // Retract and destroy the manager + // + retract($manager); + $manager.destroy(); +end + +/* +* +* This rule will clean up any rogue events where there is no +* ControlLoopParams object corresponding to the onset event. +* +*/ +rule "EVENT.CLEANUP" + salience -1 + when + $event : VirtualControlLoopEvent( $clName: closedLoopControlName ) + then + + Logger logger = LoggerFactory.getLogger(drools.getRule().getPackage()); + logger.info("{}: {}", $clName, drools.getRule().getName()); + logger.debug("{}: {}: orphan event={}", + $clName, drools.getRule().getName(), $event); + // + // Retract the event + // + retract($event); +end + +/* +* +* At this point, it appears that if we prevent the rules from getting messages from +* topics, then that will also prevent the actors from getting them. So the following +* rules are here just to discard those messages. +* +* These have a higher salience so the objects are removed before the "FINAL" message +* is processed, so that the junit test can assume things are done once they see the +* "FINAL" message. Otherwise, tests might fail sporadically. +* +*/ +rule "APPC.Response.CLEANUP" + salience 1 + when + $msg : org.onap.policy.appc.Response( ) + then + retract($msg); +end + +rule "APPC.Request.CLEANUP" + salience 1 + when + $msg : org.onap.policy.appc.Request( ) + then + retract($msg); +end + +rule "APPC-LCM.Response.CLEANUP" + salience 1 + when + $msg : org.onap.policy.appclcm.AppcLcmDmaapWrapper( ) + then + retract($msg); +end + +rule "SDNR.Response.CLEANUP" + salience 1 + when + $msg : org.onap.policy.sdnr.PciResponseWrapper( ) + then + retract($msg); +end diff --git a/controlloop/common/controller-frankfurt/src/test/java/org/onap/policy/controlloop/FrankfurtBase.java b/controlloop/common/controller-frankfurt/src/test/java/org/onap/policy/controlloop/FrankfurtBase.java new file mode 100644 index 000000000..6e3380ee4 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/java/org/onap/policy/controlloop/FrankfurtBase.java @@ -0,0 +1,558 @@ +/*- + * ============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; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.LinkedList; +import java.util.Properties; +import java.util.Queue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import lombok.Getter; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.kie.api.event.rule.AfterMatchFiredEvent; +import org.kie.api.event.rule.BeforeMatchFiredEvent; +import org.kie.api.event.rule.DefaultAgendaEventListener; +import org.kie.api.event.rule.DefaultRuleRuntimeEventListener; +import org.kie.api.event.rule.MatchCancelledEvent; +import org.kie.api.event.rule.MatchCreatedEvent; +import org.kie.api.event.rule.ObjectDeletedEvent; +import org.kie.api.event.rule.ObjectInsertedEvent; +import org.kie.api.event.rule.ObjectUpdatedEvent; +import org.kie.api.event.rule.RuleRuntimeEventListener; +import org.kie.api.runtime.KieSession; +import org.onap.policy.common.endpoints.event.comm.Topic; +import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.TopicListener; +import org.onap.policy.common.endpoints.http.client.HttpClientConfigException; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.common.endpoints.http.server.HttpServletServerFactoryInstance; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardCoder; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.drools.persistence.SystemPersistence; +import org.onap.policy.drools.persistence.SystemPersistenceConstants; +import org.onap.policy.drools.protocol.coders.EventProtocolCoderConstants; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyControllerConstants; +import org.onap.policy.drools.system.PolicyEngine; +import org.onap.policy.drools.system.PolicyEngineConstants; +import org.onap.policy.drools.util.KieUtils; +import org.onap.policy.drools.utils.logging.LoggerUtil; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; +import org.onap.policy.simulators.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Use Cases Tests Framework. + */ +public abstract class FrankfurtBase { + + private static final Logger logger = LoggerFactory.getLogger(FrankfurtBase.class); + private static final StandardCoder coder = new StandardCoder(); + + /** + * PDP-D Engine. + */ + protected static final PolicyEngine pdpD = PolicyEngineConstants.getManager(); + + /** + * PDP-D Configuration Repository. + */ + protected static final SystemPersistence repo = SystemPersistenceConstants.getManager(); + + /** + * Frankfurt controller and session name. + */ + protected static final String CONTROLLER_NAME = "frankfurt"; + + /** + * Frankfurt controller. + */ + protected static PolicyController controller; + + /* + * Canonical Topic Names. + */ + protected static final String DCAE_TOPIC = "DCAE_TOPIC"; + protected static final String APPC_LCM_WRITE_TOPIC = "APPC-LCM-WRITE"; + protected static final String POLICY_CL_MGT_TOPIC = "POLICY-CL-MGT"; + protected static final String APPC_LCM_READ_TOPIC = "APPC-LCM-READ"; + protected static final String APPC_CL_TOPIC = "APPC-CL"; + + protected static void initConfigDir() { + SystemPersistenceConstants.getManager().setConfigurationDir("src/test/resources/config"); + } + + /** + * Sets up overall logging. + */ + protected static void setupLogging() { + LoggerUtil.setLevel(LoggerUtil.ROOT_LOGGER, "WARN"); + LoggerUtil.setLevel("org.eclipse.jetty", "WARN"); + LoggerUtil.setLevel("org.onap.policy.controlloop", "INFO"); + LoggerUtil.setLevel("network", "INFO"); + } + + /** + * Sets up Drools Logging for events of interest. + */ + protected static void setupDroolsLogging() { + KieSession session = PolicyControllerConstants.getFactory().get(CONTROLLER_NAME).getDrools().getContainer() + .getPolicySession(CONTROLLER_NAME).getKieSession(); + + session.addEventListener(new RuleListenerLogger()); + session.addEventListener(new AgendaListenerLogger()); + } + + /** + * Sets up Http Clients specified in the property file. + */ + protected static void setUpHttpClients() { + try { + HttpClientFactoryInstance.getClientFactory().build( + SystemPersistenceConstants.getManager().getHttpClientProperties("frankfurt")); + } catch (HttpClientConfigException e) { + throw new IllegalArgumentException("cannot initialize HTTP clients", e); + } + } + + /** + * Sets up Simulators for use case testing. + */ + protected static void setupSimulators() throws InterruptedException { + Util.buildAaiSim(); + Util.buildSoSim(); + Util.buildVfcSim(); + Util.buildGuardSim(); + Util.buildSdncSim(); + } + + /** + * Returns the runtime Control Loop Parameters associated with a Tosca Policy. + */ + protected ControlLoopParams clParameters(ToscaPolicy policy) { + return controller.getDrools().facts(CONTROLLER_NAME, ControlLoopParams.class).stream() + .filter((params) -> params.getToscaPolicy() == policy).findFirst().get(); + } + + protected ToscaPolicy getPolicyFromResource(String resourcePath, String policyName) throws CoderException { + String policyJson = ResourceUtils.getResourceAsString(resourcePath); + ToscaServiceTemplate serviceTemplate = coder.decode(policyJson, ToscaServiceTemplate.class); + ToscaPolicy policy = serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName); + assertNotNull(policy); + + /* + * name and version are used within a drl. api component and drools core will + * ensure that these are populated. + */ + if (StringUtils.isBlank(policy.getName())) { + policy.setName(policyName); + } + + if (StringUtils.isBlank(policy.getVersion())) { + policy.setVersion(policy.getTypeVersion()); + } + + return serviceTemplate.getToscaTopologyTemplate().getPolicies().get(0).get(policyName); + } + + protected ToscaPolicy getPolicyFromFile(String policyPath) throws IOException, CoderException { + String rawPolicy = new String(Files.readAllBytes(Paths.get(policyPath))); + return coder.decode(rawPolicy, ToscaPolicy.class); + } + + private ToscaPolicy setupPolicy(ToscaPolicy policy) throws InterruptedException { + final KieObjectExpectedCallback<?> policyTracker = new KieObjectInsertedExpectedCallback<>(policy); + final KieObjectExpectedCallback<?> paramsTracker = + new KieClassInsertedExpectedCallback<>(ControlLoopParams.class); + + controller.getDrools().offer(policy); + + assertTrue(policyTracker.isNotified()); + assertTrue(paramsTracker.isNotified()); + + assertEquals(1, controller.getDrools().facts(CONTROLLER_NAME, ToscaPolicy.class).stream() + .filter((anotherPolicy) -> anotherPolicy == policy).count()); + + assertEquals(1, controller.getDrools().facts(CONTROLLER_NAME, ControlLoopParams.class).stream() + .filter((params) -> params.getToscaPolicy() == policy).count()); + return policy; + } + + /** + * Installs a policy from policy/models (examples) repo. + */ + protected ToscaPolicy setupPolicyFromResource(String resourcePath, String policyName) + throws CoderException, InterruptedException { + return setupPolicy(getPolicyFromResource(resourcePath, policyName)); + } + + + /** + * Installs a given policy. + */ + protected ToscaPolicy setupPolicyFromFile(String policyPath) + throws IOException, CoderException, InterruptedException { + return setupPolicy(getPolicyFromFile(policyPath)); + } + + /** + * Deletes a policy. + */ + protected void deletePolicy(ToscaPolicy policy) throws InterruptedException { + ControlLoopParams clParams = clParameters(policy); + assertNotNull(clParams); + + final KieObjectExpectedCallback<?> policyTracker = new KieObjectDeletedExpectedCallback<>(policy); + final KieObjectExpectedCallback<?> clParamsTracker = new KieObjectDeletedExpectedCallback<>(clParams); + + controller.getDrools().delete(CONTROLLER_NAME, policy); + assertTrue(policyTracker.isNotified()); + assertTrue(clParamsTracker.isNotified()); + + assertEquals(0, controller.getDrools().facts(CONTROLLER_NAME, ToscaPolicy.class).stream() + .filter((anotherPolicy) -> anotherPolicy == policy).count()); + + assertEquals(0, controller.getDrools().facts(CONTROLLER_NAME, ControlLoopParams.class).stream() + .filter((params) -> params.getPolicyName() == policy.getName()).count()); + } + + /** + * Prepare a PDP-D to test the Use Cases. + */ + protected static void preparePdpD() throws IOException { + KieUtils.installArtifact(Paths.get("src/main/resources/META-INF/kmodule.xml").toFile(), + Paths.get("src/test/resources/frankfurt.pom").toFile(), + "src/main/resources/org/onap/policy/controlloop/", + Collections.singletonList(Paths.get("src/main/resources/frankfurt.drl").toFile())); + + repo.setConfigurationDir("src/test/resources/config"); + pdpD.configure(new Properties()); + + controller = pdpD.createPolicyController(CONTROLLER_NAME, repo.getControllerProperties(CONTROLLER_NAME)); + pdpD.start(); + + setupDroolsLogging(); + } + + /** + * Stop PDP-D. + */ + protected static void stopPdpD() { + PolicyControllerConstants.getFactory().shutdown(CONTROLLER_NAME); + pdpD.stop(); + } + + /** + * Stops the http clients. + */ + protected static void stopHttpClients() { + HttpClientFactoryInstance.getClientFactory().destroy(); + } + + /** + * Stop Simulators. + */ + protected static void stopSimulators() { + HttpServletServerFactoryInstance.getServerFactory().destroy(); + } + + /** + * Creates a Topic Sink Callback tracker. + */ + protected <T> TopicCallback<T> createTopicSinkCallback(String topicName, Class<T> clazz) { + return new TopicCallback<>(TopicEndpointManager.getManager().getNoopTopicSink(topicName), clazz); + } + + /** + * Creates a Topic Sink Callback tracker. + */ + protected <T> TopicCallback<T> createTopicSinkCallbackPlain(String topicName, Class<T> clazz, Coder coder) { + return new TopicCallbackCoder<>(TopicEndpointManager.getManager().getNoopTopicSink(topicName), clazz, coder); + } + + /** + * Creates a Topic Source Callback tracker. + */ + protected <T> TopicCallback<T> createTopicSourceCallback(String topicName, Class<T> clazz) { + return new TopicCallback<>(TopicEndpointManager.getManager().getNoopTopicSource(topicName), clazz); + } + + /** + * Injects a message on a Topic Source. + */ + protected void injectOnTopic(String topicName, Path onsetPath) throws IOException { + TopicEndpointManager.getManager().getNoopTopicSource(topicName) + .offer(new String(Files.readAllBytes(onsetPath))); + } + + /** + * Injects a message on a Topic Source, with the given substitution.. + */ + protected void injectOnTopic(String topicName, Path path, String newText) throws IOException { + String text = IOUtils.toString(path.toUri(), StandardCharsets.UTF_8); + text = text.replace("${replaceMe}", newText); + TopicEndpointManager.getManager().getNoopTopicSource(topicName).offer(text); + } + + /** + * Waits for LOCK acquisition and getting a Permit from PDP-X to proceed. + */ + protected void waitForLockAndPermit(ToscaPolicy policy, TopicCallback<VirtualControlLoopNotification> policyClMgt) { + String policyName = policy.getIdentifier().getName(); + + // TODO register a topic listener instead of using await() ? + + await().until(() -> !policyClMgt.getMessages().isEmpty()); + VirtualControlLoopNotification notif = policyClMgt.getMessages().remove(); + assertEquals(ControlLoopNotificationType.ACTIVE, notif.getNotification()); + assertEquals(policyName + ".EVENT", notif.getPolicyName()); + + await().until(() -> !policyClMgt.getMessages().isEmpty()); + notif = policyClMgt.getMessages().remove(); + assertEquals(ControlLoopNotificationType.OPERATION, notif.getNotification()); + assertEquals(policyName + ".EVENT.MANAGER.PROCESSING", notif.getPolicyName()); + assertThat(notif.getMessage()).startsWith("Sending guard query"); + + await().until(() -> !policyClMgt.getMessages().isEmpty()); + notif = policyClMgt.getMessages().remove(); + assertEquals(ControlLoopNotificationType.OPERATION, notif.getNotification()); + assertEquals(policyName + ".EVENT.MANAGER.PROCESSING", notif.getPolicyName()); + assertThat(notif.getMessage()).startsWith("Guard result").endsWith("Permit"); + } + + /** + * Waits for a FINAL SUCCESS transaction notification. + */ + protected void waitForFinalSuccess(ToscaPolicy policy, TopicCallback<VirtualControlLoopNotification> policyClMgt) { + await().until(() -> !policyClMgt.getMessages().isEmpty()); + assertEquals(ControlLoopNotificationType.FINAL_SUCCESS, policyClMgt.getMessages().peek().getNotification()); + assertEquals(policy.getIdentifier().getName() + ".EVENT.MANAGER.FINAL", + policyClMgt.getMessages().remove().getPolicyName()); + } + + /** + * Logs Modifications to Working Memory. + */ + static class RuleListenerLogger implements RuleRuntimeEventListener { + @Override + public void objectInserted(ObjectInsertedEvent event) { + String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null"; + logger.info("RULE {}: inserted {}", ruleName, event.getObject()); + } + + @Override + public void objectUpdated(ObjectUpdatedEvent event) { + String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null"; + logger.info("RULE {}: updated {}", ruleName, event.getObject()); + + } + + @Override + public void objectDeleted(ObjectDeletedEvent event) { + String ruleName = (event.getRule() != null) ? event.getRule().getName() : "null"; + logger.info("RULE {}: deleted {}", ruleName, event.getOldObject()); + } + } + + /** + * Logs Rule Matches. + */ + static class AgendaListenerLogger extends DefaultAgendaEventListener { + @Override + public void matchCreated(MatchCreatedEvent event) { + logger.info("RULE {}: match created", event.getMatch().getRule().getName()); + } + + @Override + public void matchCancelled(MatchCancelledEvent event) { + logger.info("RULE {}: match cancelled", event.getMatch().getRule().getName()); + } + + @Override + public void beforeMatchFired(BeforeMatchFiredEvent event) { + logger.info("RULE {}: before match fired", event.getMatch().getRule().getName()); + } + + @Override + public void afterMatchFired(AfterMatchFiredEvent event) { + logger.info("RULE {}: after match fired", event.getMatch().getRule().getName()); + } + } + + /** + * Base Class to track Working Memory updates for objects of type T. + */ + abstract class KieObjectExpectedCallback<T> extends DefaultRuleRuntimeEventListener { + protected T subject; + + protected CountDownLatch countDownLatch = new CountDownLatch(1); + + public KieObjectExpectedCallback(T affected) { + subject = affected; + register(); + } + + public boolean isNotified() throws InterruptedException { + return countDownLatch.await(9L, TimeUnit.SECONDS); + } + + protected void callbacked() { + unregister(); + countDownLatch.countDown(); + } + + public KieObjectExpectedCallback<T> register() { + controller.getDrools().getContainer().getPolicySession(CONTROLLER_NAME).getKieSession() + .addEventListener(this); + return this; + } + + public KieObjectExpectedCallback<T> unregister() { + controller.getDrools().getContainer().getPolicySession(CONTROLLER_NAME).getKieSession() + .removeEventListener(this); + return this; + } + } + + /** + * Tracks inserts in Working Memory for an object of type T. + */ + class KieObjectInsertedExpectedCallback<T> extends KieObjectExpectedCallback<T> { + public KieObjectInsertedExpectedCallback(T affected) { + super(affected); + } + + @Override + public void objectInserted(ObjectInsertedEvent event) { + if (subject == event.getObject()) { + callbacked(); + } + } + } + + /** + * Tracks deletes in Working Memory of an object of type T. + */ + class KieObjectDeletedExpectedCallback<T> extends KieObjectExpectedCallback<T> { + public KieObjectDeletedExpectedCallback(T affected) { + super(affected); + } + + @Override + public void objectDeleted(ObjectDeletedEvent event) { + if (subject == event.getOldObject()) { + callbacked(); + } + } + } + + /** + * Tracks inserts in Working Memory for any object of class T. + */ + class KieClassInsertedExpectedCallback<T> extends KieObjectInsertedExpectedCallback<T> { + + public KieClassInsertedExpectedCallback(T affected) { + super(affected); + } + + public void objectInserted(ObjectInsertedEvent event) { + if (subject == event.getObject().getClass()) { + callbacked(); + } + } + } + + /** + * Tracks callbacks from topics. + */ + class TopicCallback<T> implements TopicListener { + protected final Topic topic; + protected final Class<T> expectedClass; + + @Getter + protected Queue<T> messages = new LinkedList<>(); + + public TopicCallback(Topic topic, Class<T> expectedClass) { + this.topic = topic; + this.expectedClass = expectedClass; + this.topic.register(this); + } + + public TopicCallback<T> register() { + this.topic.register(this); + return this; + } + + public TopicCallback<T> unregister() { + this.topic.unregister(this); + return this; + } + + @Override + @SuppressWarnings("unchecked") + public void onTopicEvent(CommInfrastructure comm, String topic, String event) { + try { + messages.add((T) EventProtocolCoderConstants.getManager().decode(controller.getDrools().getGroupId(), + controller.getDrools().getArtifactId(), topic, event)); + } catch (Exception e) { + logger.warn("invalid mapping in topic {} for event {}", topic, event, e); + } + } + } + + class TopicCallbackCoder<T> extends TopicCallback<T> { + private final Coder coder; + + public TopicCallbackCoder(Topic topic, Class<T> expectedClass, Coder coder) { + super(topic, expectedClass); + this.coder = coder; + } + + @Override + public void onTopicEvent(CommInfrastructure comm, String topic, String event) { + try { + messages.add((T) coder.decode(event, expectedClass)); + } catch (Exception e) { + logger.warn("invalid mapping in topic {} for event {}", topic, event, e); + } + } + + } +} diff --git a/controlloop/common/controller-frankfurt/src/test/java/org/onap/policy/controlloop/VlbTest.java b/controlloop/common/controller-frankfurt/src/test/java/org/onap/policy/controlloop/VlbTest.java new file mode 100644 index 000000000..4e5a6e3c7 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/java/org/onap/policy/controlloop/VlbTest.java @@ -0,0 +1,152 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.nio.file.Paths; +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.utils.coder.CoderException; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; + +/** + * VLB Use Case Tests. + */ +public class VlbTest extends FrankfurtBase { + + /** + * VLB Tosca Policy File. + */ + private static final String TOSCA_LEGACY_POLICY_VLB = "src/test/resources/vlb/tosca-vlb.json"; + private static final String TOSCA_COMPLIANT_POLICY_VLB = "src/test/resources/vlb/tosca-compliant-vlb.json"; + + /* + * VLB Use case Messages. + */ + private static final String ONSET = "src/test/resources/vlb/vlb.onset.json"; + + /* + * Topic trackers used by the VLB use case. + */ + private TopicCallback<VirtualControlLoopNotification> policyClMgt; + + /* + * VLB Tosca Policy. + */ + private ToscaPolicy policy; + + /** + * Prepare PDP-D Framework for testing. + */ + @BeforeClass + public static void prepareResouces() throws InterruptedException, IOException { + initConfigDir(); + setupLogging(); + preparePdpD(); + setUpHttpClients(); + setupSimulators(); + } + + /** + * Take down the resources used by the test framework. + */ + @AfterClass + public static void takeDownResources() { + stopPdpD(); + stopSimulators(); + } + + /** + * Observe Topics. + */ + @Before + public void topicsRegistration() { + policyClMgt = createTopicSinkCallback(POLICY_CL_MGT_TOPIC, VirtualControlLoopNotification.class); + } + + /** + * Unregister Topic Callbacks and uninstall the policy. + */ + @After + public void topicsUnregistration() throws InterruptedException { + if (policyClMgt != null) { + policyClMgt.unregister(); + } + + // uninstall the policy + assertEquals(2, controller.getDrools().factCount(CONTROLLER_NAME)); + if (policy != null) { + deletePolicy(policy); + } + assertEquals(0, controller.getDrools().factCount(CONTROLLER_NAME)); + } + + /** + * Sunny Day with Legacy Tosca Policy. + */ + @Test + public void sunnyDayLegacy() throws InterruptedException, CoderException, IOException { + assertEquals(0, controller.getDrools().factCount(CONTROLLER_NAME)); + policy = setupPolicyFromFile(TOSCA_LEGACY_POLICY_VLB); + assertEquals(2, controller.getDrools().factCount(CONTROLLER_NAME)); + + sunnyDay(); + } + + /** + * Sunny Day with Tosca Compliant Policy. + */ + @Test + public void sunnyDayCompliant() throws InterruptedException, CoderException, IOException { + assertEquals(0, controller.getDrools().factCount(CONTROLLER_NAME)); + policy = setupPolicyFromFile(TOSCA_COMPLIANT_POLICY_VLB); + assertEquals(2, controller.getDrools().factCount(CONTROLLER_NAME)); + + sunnyDay(); + } + + /** + * Sunny day scenario for the VCPE use case. + */ + private void sunnyDay() throws IOException { + + /* Inject an ONSET event over the DCAE topic */ + injectOnTopic(DCAE_TOPIC, Paths.get(ONSET)); + + /* Wait to acquire a LOCK and a PDP-X PERMIT */ + waitForLockAndPermit(policy, policyClMgt); + + /* Ensure that the VLB SO Operation was successfully completed */ + + await().until(() -> !policyClMgt.getMessages().isEmpty()); + assertEquals(ControlLoopNotificationType.OPERATION_SUCCESS, + policyClMgt.getMessages().remove().getNotification()); + + /* --- VLB Transaction Completed --- */ + waitForFinalSuccess(policy, policyClMgt); + } +} diff --git a/controlloop/common/controller-frankfurt/src/test/resources/config/event-manager.properties b/controlloop/common/controller-frankfurt/src/test/resources/config/event-manager.properties new file mode 100644 index 000000000..7416d0480 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/config/event-manager.properties @@ -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======================================================== +# + +# DB parameters +operation.history.url=jdbc:h2:mem:Frankfurt +operation.history.userName=sa +operation.history.password= + +# Actor parameters +# +# Note: every operation must have at least one entry, otherwise it will not be +# configured and started. Thus some of them have a "placeholder" property. +# + +actor.service.GUARD.disabled=true +actor.service.GUARD.clientName=GUARD +actor.service.GUARD.operations.Decision.path=decision + +actor.service.AAI.clientName=AAI +actor.service.AAI.operations.CustomQuery.path=aai/v16/query +actor.service.AAI.operations.Tenant.path=aai/v16/search/nodes-query + +actor.service.APPC.sinkTopic=APPC-LCM-READ +actor.service.APPC.sourceTopic=APPC-LCM-WRITE +actor.service.APPC.operations.ConfigModify.placeholder= +actor.service.APPC.operations.Migrate.placeholder= +actor.service.APPC.operations.Restart.placeholder= +actor.service.APPC.operations.Rebuild.placeholder= + +# legacy APPC - must specify sink and source for each operation +actor.service.APPC.operations.ModifyConfig.sinkTopic=APPC-CL +actor.service.APPC.operations.ModifyConfig.sourceTopic=APPC-CL + +actor.service.CDS.clientName=CDS +actor.service.CDS.grpcHost=localhost +actor.service.CDS.grpcPort=7878 +actor.service.CDS.grpcUsername=grpc-username +actor.service.CDS.grpcPassword=grpc-password +actor.service.CDS.grpcTimeout=10 +actor.service.CDS.operations.xxx.yyy= + +actor.service.SDNC.clientName=SDNC +actor.service.SDNC.operations.xxx.yyy= + +actor.service.SO.clientName=SO +actor.service.SO.pathGet=orchestrationRequests/v5/ +actor.service.SO.maxGets=20 +actor.service.SO.waitSecGet=20 +actor.service.SO.operations.VF\ Module\ Create.path=serviceInstantiation/v7/serviceInstances +actor.service.SO.operations.VF\ Module\ Delete.path=serviceInstances/v7 + +actor.service.VFC.clientName=VFC +actor.service.VFC.operations.xxx.yyy= diff --git a/controlloop/common/controller-frankfurt/src/test/resources/config/frankfurt-controller.properties b/controlloop/common/controller-frankfurt/src/test/resources/config/frankfurt-controller.properties new file mode 100644 index 000000000..694f66e61 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/config/frankfurt-controller.properties @@ -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========================================================= +# + +controller.name=frankfurt + +rules.groupId=org.onap.policy.controlloop +rules.artifactId=frankfurt +rules.version=1.0.0 + +noop.source.topics=DCAE_TOPIC,APPC-CL,APPC-LCM-WRITE,SDNR-CL-RSP,POLICY-CL-MGT,APPC-LCM-READ,SDNR-CL,DCAE_CL_RSP + +noop.source.topics.DCAE_TOPIC.events=\ + org.onap.policy.controlloop.CanonicalOnset,org.onap.policy.controlloop.CanonicalAbated +noop.source.topics.DCAE_TOPIC.events.org.onap.policy.controlloop.CanonicalOnset.filter=\ + [?($.closedLoopEventStatus == 'ONSET')] +noop.source.topics.DCAE_TOPIC.events.org.onap.policy.controlloop.CanonicalAbated.filter=\ + [?($.closedLoopEventStatus == 'ABATED')] +noop.source.topics.DCAE_TOPIC.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gson + +noop.source.topics.APPC-CL.events=org.onap.policy.appc.Response,org.onap.policy.appc.Request +noop.source.topics.APPC-CL.events.org.onap.policy.appc.Response.filter=[?($.CommonHeader && $.Status)] +noop.source.topics.APPC-CL.events.org.onap.policy.appc.Request.filter=[?($.CommonHeader && $.Action)] +noop.source.topics.APPC-CL.events.custom.gson=org.onap.policy.appc.util.Serialization,gsonPretty + +noop.source.topics.APPC-LCM-WRITE.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper +noop.source.topics.APPC-LCM-WRITE.events.org.onap.policy.appclcm.AppcLcmDmaapWrapper.filter=[?($.type == 'response')] +noop.source.topics.APPC-LCM-WRITE.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson + +noop.source.topics.SDNR-CL-RSP.events=org.onap.policy.sdnr.PciResponseWrapper +noop.source.topics.SDNR-CL-RSP.events.org.onap.policy.sdnr.PciResponseWrapper.filter=[?($.type == 'response')] +noop.source.topics.SDNR-CL-RSP.events.custom.gson=org.onap.policy.sdnr.util.Serialization,gson + +noop.source.topics.POLICY-CL-MGT.events=org.onap.policy.controlloop.VirtualControlLoopNotification +noop.source.topics.POLICY-CL-MGT.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty + +noop.source.topics.APPC-LCM-READ.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper +noop.source.topics.APPC-LCM-READ.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson + +noop.source.topics.SDNR-CL.events=org.onap.policy.sdnr.PciRequestWrapper +noop.source.topics.SDNR-CL.events.custom.gson=org.onap.policy.sdnr.util.Serialization,gson + +noop.source.topics.DCAE_CL_RSP.events=org.onap.policy.controlloop.ControlLoopResponse +noop.source.topics.DCAE_CL_RSP.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty + +noop.sink.topics=APPC-CL,APPC-LCM-READ,POLICY-CL-MGT,SDNR-CL,DCAE_CL_RSP + +noop.sink.topics.POLICY-CL-MGT.events=org.onap.policy.controlloop.VirtualControlLoopNotification +noop.sink.topics.POLICY-CL-MGT.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty diff --git a/controlloop/common/controller-frankfurt/src/test/resources/config/frankfurt-http-client.properties b/controlloop/common/controller-frankfurt/src/test/resources/config/frankfurt-http-client.properties new file mode 100644 index 000000000..bb3a1a02b --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/config/frankfurt-http-client.properties @@ -0,0 +1,52 @@ +# +# ============LICENSE_START======================================================= +# ONAP +# ================================================================================ +# Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============LICENSE_END========================================================= +# + +http.client.services=GUARD,AAI,SDNC,SO,VFC + +http.client.services.GUARD.managed=true +http.client.services.GUARD.host=localhost +http.client.services.GUARD.port=6669 +http.client.services.GUARD.username=pdpx +http.client.services.GUARD.password=pdpx +http.client.services.GUARD.contextUriPath=policy/pdpx/v1 + +http.client.services.AAI.managed=true +http.client.services.AAI.host=localhost +http.client.services.AAI.port=6666 +http.client.services.AAI.contextUriPath= + +http.client.services.SDNC.managed=true +http.client.services.SDNC.host=localhost +http.client.services.SDNC.port=6665 +http.client.services.SDNC.username=sdnc +http.client.services.SDNC.password=sdnc +http.client.services.SDNC.contextUriPath= + +http.client.services.SO.managed=true +http.client.services.SO.host=localhost +http.client.services.SO.port=6667 +http.client.services.SO.contextUriPath= + +http.client.services.VFC.managed=true +http.client.services.VFC.host=localhost +http.client.services.VFC.port=6668 +http.client.services.VFC.username=VFC +http.client.services.VFC.password=VFC +http.client.services.VFC.contextUriPath=api/nslcm/v1 diff --git a/controlloop/common/controller-frankfurt/src/test/resources/frankfurt.pom b/controlloop/common/controller-frankfurt/src/test/resources/frankfurt.pom new file mode 100644 index 000000000..707e59c9e --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/frankfurt.pom @@ -0,0 +1,30 @@ +<?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========================================================= + --> + +<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> + + <groupId>org.onap.policy.controlloop</groupId> + <artifactId>frankfurt</artifactId> + <version>1.0.0</version> +</project> diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vcpe/tosca-compliant-vcpe.json b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/tosca-compliant-vcpe.json new file mode 100644 index 000000000..b876446dd --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/tosca-compliant-vcpe.json @@ -0,0 +1,37 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "version": "1.0.0", + "name": "operational.restart", + "metadata": { + "policy-id": "operational.restart" + }, + "properties": { + "id": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "timeout": 3600, + "abatement": false, + "trigger": "unique-policy-id-1-restart", + "operations": [ + { + "id": "unique-policy-id-1-restart", + "description": "Restart the VM", + "operation": { + "actor": "APPC", + "operation": "Restart", + "target": { + "targetType": "VM" + } + }, + "timeout": 1200, + "retries": 3, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ], + "controllerName": "frankfurt" + } +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vcpe/tosca-legacy-vcpe.json b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/tosca-legacy-vcpe.json new file mode 100644 index 000000000..f42c07da9 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/tosca-legacy-vcpe.json @@ -0,0 +1,9 @@ +{ + "type": "onap.policies.controlloop.Operational", + "type_version": "1.0.0", + "properties": { + "content": "controlLoop%3A%0A%20%20version%3A%202.0.0%0A%20%20controlLoopName%3A%20ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e%0A%20%20trigger_policy%3A%20unique-policy-id-1-restart%0A%20%20timeout%3A%203600%0A%20%20abatement%3A%20false%0A%20%0Apolicies%3A%0A%20%20-%20id%3A%20unique-policy-id-1-restart%0A%20%20%20%20name%3A%20Restart%20the%20VM%0A%20%20%20%20description%3A%0A%20%20%20%20actor%3A%20APPC%0A%20%20%20%20recipe%3A%20Restart%0A%20%20%20%20target%3A%0A%20%20%20%20%20%20type%3A%20VM%0A%20%20%20%20retry%3A%203%0A%20%20%20%20timeout%3A%201200%0A%20%20%20%20success%3A%20final_success%0A%20%20%20%20failure%3A%20final_failure%0A%20%20%20%20failure_timeout%3A%20final_failure_timeout%0A%20%20%20%20failure_retries%3A%20final_failure_retries%0A%20%20%20%20failure_exception%3A%20final_failure_exception%0A%20%20%20%20failure_guard%3A%20final_failure_guard" + }, + "name": "vcpe", + "version": "1.0.0" +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.appc.success.json b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.appc.success.json new file mode 100644 index 000000000..331021798 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.appc.success.json @@ -0,0 +1,22 @@ +{ + "body": { + "output": { + "common-header": { + "timestamp": "2017-08-25T21:06:23.037Z", + "api-ver": "5.00", + "originator-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "request-id": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "sub-request-id": "${replaceMe}", + "flags": {} + }, + "status": { + "code": 400, + "message": "Restart Successful" + } + } + }, + "version": "2.0", + "rpc-name": "restart", + "correlation-id": "664be3d2-6c12-4f4b-a3e7-c349acced200-1", + "type": "response" +} diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.1.json b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.1.json new file mode 100644 index 000000000..d08ee47cd --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.1.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "664be3d2-6c12-4f4b-a3e7-c349acced200", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.2.json b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.2.json new file mode 100644 index 000000000..b8c76514e --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.2.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "8cf3cd05-1218-4224-931b-601494ffe55b", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.3.json b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.3.json new file mode 100644 index 000000000..40f29b3cf --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vcpe/vcpe.onset.3.json @@ -0,0 +1,17 @@ +{ + "closedLoopControlName": "ControlLoop-vCPE-48f0c2c3-a172-4192-9ae3-052274181b6e", + "closedLoopAlarmStart": 1570722876324905, + "closedLoopAlarmEnd": 1570722876324999, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "8cf3cd05-1218-4224-931b-601494ffe55b", + "target_type": "VNF", + "target": "generic-vnf.vnf-id", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-id": "vCPE_Infrastructure_vGMUX_demo_app" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vfw/tosca-compliant-vfw.json b/controlloop/common/controller-frankfurt/src/test/resources/vfw/tosca-compliant-vfw.json new file mode 100644 index 000000000..47cb09d45 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vfw/tosca-compliant-vfw.json @@ -0,0 +1,40 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "name": "operational.modifyconfig", + "version": "1.0.0", + "metadata": { + "policy-id": "operational.modifyconfig" + }, + "properties": { + "id": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a", + "timeout": 60, + "abatement": false, + "trigger": "unique-policy-id-1-modifyConfig", + "operations": [ + { + "id": "unique-policy-id-1-modifyConfig", + "description": "Modify the packet generator", + "operation": { + "actor": "APPC", + "operation": "ModifyConfig", + "target": { + "targetType": "VNF", + "entityIds": { + "resourceID": "bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38" + } + } + }, + "timeout": 300, + "retries": 0, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ], + "controllerName": "frankfurt" + } +} diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vfw/tosca-vfw.json b/controlloop/common/controller-frankfurt/src/test/resources/vfw/tosca-vfw.json new file mode 100644 index 000000000..5d1e35277 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vfw/tosca-vfw.json @@ -0,0 +1,9 @@ +{ + "type": "onap.policies.controlloop.Operational", + "type_version": "1.0.0", + "properties": { + "content": "controlLoop%3A%0A%20%20version%3A%202.0.0%0A%20%20controlLoopName%3A%20ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a%0A%20%20services%3A%0A%20%20%20%20-%20serviceInvariantUUID%3A%20f6937c86-584c-47ae-ad29-8d41d6f0cc7c%0A%20%20%20%20%20%20serviceUUID%3A%207be584e2-0bb2-4126-adaf-ced2c77ca0b3%0A%20%20%20%20%20%20serviceName%3A%20Service_Ete_Name7ba1fbde-6187-464a-a62d-d9dd25bdf4e8%0A%20%20trigger_policy%3A%20unique-policy-id-1-modifyConfig%0A%20%20timeout%3A%2060%0A%20%20abatement%3A%20false%0A%20%0Apolicies%3A%0A%20%20-%20id%3A%20unique-policy-id-1-modifyConfig%0A%20%20%20%20name%3A%20modify%20packet%20gen%20config%0A%20%20%20%20description%3A%0A%20%20%20%20actor%3A%20APPC%0A%20%20%20%20recipe%3A%20ModifyConfig%0A%20%20%20%20target%3A%0A%20%20%20%20%20%20resourceID%3A%20bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38%0A%20%20%20%20%20%20type%3A%20VNF%0A%20%20%20%20retry%3A%200%0A%20%20%20%20timeout%3A%2030%0A%20%20%20%20success%3A%20final_success%0A%20%20%20%20failure%3A%20final_failure%0A%20%20%20%20failure_timeout%3A%20final_failure_timeout%0A%20%20%20%20failure_retries%3A%20final_failure_retries%0A%20%20%20%20failure_exception%3A%20final_failure_exception%0A%20%20%20%20failure_guard%3A%20final_failure_guard" + }, + "name": "vfw", + "version": "1.0.0" +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vfw/vfw.appc.success.json b/controlloop/common/controller-frankfurt/src/test/resources/vfw/vfw.appc.success.json new file mode 100644 index 000000000..d7e6ec3b9 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vfw/vfw.appc.success.json @@ -0,0 +1,17 @@ +{ + "CommonHeader": { + "TimeStamp": 1506051879001, + "APIver": "1.01", + "RequestID": "c7c6a4aa-bb61-4a15-b831-ba1472dd4a65", + "SubRequestID": "${replaceMe}", + "RequestTrack": [], + "Flags": [] + }, + "Status": { + "Code": 400, + "Value": "SUCCESS" + }, + "Payload": { + "generic-vnf.vnf-id": "jimmy-test-vnf2" + } +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vfw/vfw.onset.json b/controlloop/common/controller-frankfurt/src/test/resources/vfw/vfw.onset.json new file mode 100644 index 000000000..7782867a1 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vfw/vfw.onset.json @@ -0,0 +1,17 @@ +{ + "closedLoopControlName": "ControlLoop-vFirewall-d0a1dfc6-94f5-4fd4-a5b5-4630b438850a", + "closedLoopAlarmStart": 1463679805324, + "closedLoopEventClient": "microservice.stringmatcher", + "closedLoopEventStatus": "ONSET", + "requestID": "c7c6a4aa-bb61-4a15-b831-ba1472dd4a65", + "target_type": "VNF", + "target": "generic-vnf.vnf-name", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "generic-vnf.vnf-name": "fw0002vm002fw002", + "vserver.vserver-name": "OzVServer" + }, + "from": "DCAE", + "version": "1.0.2" +} diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vlb/tosca-compliant-vlb.json b/controlloop/common/controller-frankfurt/src/test/resources/vlb/tosca-compliant-vlb.json new file mode 100644 index 000000000..aeb22bb0b --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vlb/tosca-compliant-vlb.json @@ -0,0 +1,48 @@ +{ + "type": "onap.policies.controlloop.operational.common.Drools", + "type_version": "1.0.0", + "name": "operational.scaleout", + "version": "1.0.0", + "metadata": { + "policy-id": "operational.scaleout" + }, + "properties": { + "id": "ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3", + "timeout": 1200, + "abatement": false, + "trigger": "unique-policy-id-1-scale-up", + "operations": [ + { + "id": "unique-policy-id-1-scale-up", + "description": "Create a new VF Module", + "operation": { + "actor": "SO", + "operation": "VF Module Create", + "target": { + "targetType": "VFMODULE", + "entityIds": { + "modelInvariantId": "e6130d03-56f1-4b0a-9a1d-e1b2ebc30e0e", + "modelVersionId": "94b18b1d-cc91-4f43-911a-e6348665f292", + "modelName": "VfwclVfwsnkBbefb8ce2bde..base_vfw..module-0", + "modelVersion": 1, + "modelCustomizationId": "47958575-138f-452a-8c8d-d89b595f8164" + } + }, + "payload": { + "requestParameters": "{\"usePreload\":true,\"userParams\":[]}", + "configurationParameters": "[{\"ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[9]\",\"oam-ip-addr\":\"$.vf-module-topology.vf-module-parameters.param[16]\",\"enabled\":\"$.vf-module-topology.vf-module-parameters.param[23]\"}]" + } + }, + "timeout": 1200, + "retries": 0, + "success": "final_success", + "failure": "final_failure", + "failure_timeout": "final_failure_timeout", + "failure_retries": "final_failure_retries", + "failure_exception": "final_failure_exception", + "failure_guard": "final_failure_guard" + } + ], + "controllerName": "frankfurt" + } +} diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vlb/tosca-vlb.json b/controlloop/common/controller-frankfurt/src/test/resources/vlb/tosca-vlb.json new file mode 100644 index 000000000..5147d9922 --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vlb/tosca-vlb.json @@ -0,0 +1,9 @@ +{ + "type": "onap.policies.controlloop.Operational", + "type_version": "1.0.0", + "properties": { + "content": "controlLoop%3A%0A%20%20version%3A%202.0.0%0A%20%20controlLoopName%3A%20ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3%0A%20%20services%3A%0A%20%20%20%20-%20serviceName%3A%20d4738992-6497-4dca-9db9%0A%20%20%20%20%20%20serviceInvariantUUID%3A%20dc112d6e-7e73-4777-9c6f-1a7fb5fd1b6f%0A%20%20%20%20%20%20serviceUUID%3A%202eea06c6-e1d3-4c3a-b9c4-478c506eeedf%0A%20%20trigger_policy%3A%20unique-policy-id-1-scale-up%0A%20%20timeout%3A%2060%0A%0Apolicies%3A%0A%20%20-%20id%3A%20unique-policy-id-1-scale-up%0A%20%20%20%20name%3A%20Create%20a%20new%20VF%20Module%0A%20%20%20%20description%3A%0A%20%20%20%20actor%3A%20SO%0A%20%20%20%20recipe%3A%20VF%20Module%20Create%0A%20%20%20%20target%3A%0A%20%20%20%20%20%20type%3A%20VFMODULE%0A%20%20%20%20%20%20modelInvariantId%3A%20e6130d03-56f1-4b0a-9a1d-e1b2ebc30e0e%0A%20%20%20%20%20%20modelVersionId%3A%2094b18b1d-cc91-4f43-911a-e6348665f292%0A%20%20%20%20%20%20modelName%3A%20VfwclVfwsnkBbefb8ce2bde..base_vfw..module-0%0A%20%20%20%20%20%20modelVersion%3A%201%0A%20%20%20%20%20%20modelCustomizationId%3A%2047958575-138f-452a-8c8d-d89b595f8164%0A%20%20%20%20payload%3A%0A%20%20%20%20%20%20requestParameters%3A%20%27%7B%22usePreload%22%3Atrue%2C%22userParams%22%3A%5B%5D%7D%27%0A%20%20%20%20%20%20configurationParameters%3A%20%27%5B%7B%22ip-addr%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B9%5D%22%2C%22oam-ip-addr%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B16%5D%22%2C%22enabled%22%3A%22%24.vf-module-topology.vf-module-parameters.param%5B23%5D%22%7D%5D%27%0A%20%20%20%20retry%3A%200%0A%20%20%20%20timeout%3A%2030%0A%20%20%20%20success%3A%20final_success%0A%20%20%20%20failure%3A%20final_failure%0A%20%20%20%20failure_timeout%3A%20final_failure_timeout%0A%20%20%20%20failure_retries%3A%20final_failure_retries%0A%20%20%20%20failure_exception%3A%20final_failure_exception%0A%20%20%20%20failure_guard%3A%20final_failure_guard%0A" + }, + "name": "vlb", + "version": "1.0.0" +}
\ No newline at end of file diff --git a/controlloop/common/controller-frankfurt/src/test/resources/vlb/vlb.onset.json b/controlloop/common/controller-frankfurt/src/test/resources/vlb/vlb.onset.json new file mode 100644 index 000000000..3360c0abb --- /dev/null +++ b/controlloop/common/controller-frankfurt/src/test/resources/vlb/vlb.onset.json @@ -0,0 +1,16 @@ +{ + "closedLoopControlName": "ControlLoop-vDNS-6f37f56d-a87d-4b85-b6a9-cc953cf779b3", + "closedLoopAlarmStart": 1484677482204798, + "closedLoopEventClient": "DCAE_INSTANCE_ID.dcae-tca", + "closedLoopEventStatus": "ONSET", + "requestID": "e4f95e0c-a013-4530-8e59-c5c5f9e539b6", + "target_type": "VNF", + "target": "vserver.vserver-name", + "AAI": { + "vserver.is-closed-loop-disabled": "false", + "vserver.prov-status": "ACTIVE", + "vserver.vserver-name": "OzVServer" + }, + "from": "DCAE", + "version": "1.0.2" +}
\ No newline at end of file diff --git a/controlloop/common/controller-usecases/pom.xml b/controlloop/common/controller-usecases/pom.xml index 795b96f99..fb29a81c7 100644 --- a/controlloop/common/controller-usecases/pom.xml +++ b/controlloop/common/controller-usecases/pom.xml @@ -36,11 +36,6 @@ <name>${project.artifactId}</name> <description>Usecases Experimental Controller</description> - <properties> - <maven.compiler.source>1.8</maven.compiler.source> - <maven.compiler.target>1.8</maven.compiler.target> - </properties> - <build> <plugins> <plugin> diff --git a/controlloop/common/eventmanager/pom.xml b/controlloop/common/eventmanager/pom.xml index 3cd069f38..3c7451501 100644 --- a/controlloop/common/eventmanager/pom.xml +++ b/controlloop/common/eventmanager/pom.xml @@ -69,6 +69,12 @@ </dependency> <dependency> <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.guard</artifactId> + <version>${policy.models.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> <artifactId>actor.so</artifactId> <version>${policy.models.version}</version> <scope>provided</scope> diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2.java new file mode 100644 index 000000000..dc2b513a6 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2.java @@ -0,0 +1,614 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2017-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.eventmanager; + +import static org.onap.policy.controlloop.ControlLoopTargetType.PNF; +import static org.onap.policy.controlloop.ControlLoopTargetType.VM; +import static org.onap.policy.controlloop.ControlLoopTargetType.VNF; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import lombok.Getter; +import lombok.ToString; +import org.apache.commons.lang3.StringUtils; +import org.drools.core.WorkingMemory; +import org.kie.api.runtime.rule.FactHandle; +import org.onap.policy.controlloop.ControlLoopEventStatus; +import org.onap.policy.controlloop.ControlLoopException; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; +import org.onap.policy.controlloop.policy.FinalResult; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.processor.ControlLoopProcessor; +import org.onap.policy.drools.core.lock.LockCallback; +import org.onap.policy.drools.system.PolicyEngineConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manager for a single control loop event. Once this has been created, the event can be + * retracted from working memory. Once this has been created, {@link #start()} should be + * invoked, and then {@link #nextStep()} should be invoked continually until + * {@link #isActive()} returns {@code false}, indicating that all steps have completed. + */ +@ToString(onlyExplicitlyIncluded = true) +public class ControlLoopEventManager2 implements ManagerContext, Serializable { + private static final Logger logger = LoggerFactory.getLogger(ControlLoopEventManager2.class); + private static final long serialVersionUID = -1216568161322872641L; + + private static final String EVENT_MANAGER_SERVICE_CONFIG = "config/event-manager.properties"; + public static final String PROV_STATUS_ACTIVE = "ACTIVE"; + private static final String VM_NAME = "VM_NAME"; + private static final String VNF_NAME = "VNF_NAME"; + public static final String GENERIC_VNF_VNF_ID = "generic-vnf.vnf-id"; + public static final String GENERIC_VNF_VNF_NAME = "generic-vnf.vnf-name"; + public static final String VSERVER_VSERVER_NAME = "vserver.vserver-name"; + public static final String GENERIC_VNF_IS_CLOSED_LOOP_DISABLED = "generic-vnf.is-closed-loop-disabled"; + public static final String VSERVER_IS_CLOSED_LOOP_DISABLED = "vserver.is-closed-loop-disabled"; + public static final String PNF_IS_IN_MAINT = "pnf.in-maint"; + public static final String GENERIC_VNF_PROV_STATUS = "generic-vnf.prov-status"; + public static final String VSERVER_PROV_STATUS = "vserver.prov-status"; + public static final String PNF_ID = "pnf.pnf-id"; + public static final String PNF_NAME = "pnf.pnf-name"; + + private static final Set<String> VALID_TARGETS = Stream + .of(VM_NAME, VNF_NAME, VSERVER_VSERVER_NAME, GENERIC_VNF_VNF_ID, GENERIC_VNF_VNF_NAME, PNF_NAME) + .map(String::toLowerCase).collect(Collectors.toSet()); + + private static final Set<String> TRUE_VALUES = Set.of("true", "t", "yes", "y"); + + public enum NewEventStatus { + FIRST_ONSET, SUBSEQUENT_ONSET, FIRST_ABATEMENT, SUBSEQUENT_ABATEMENT, SYNTAX_ERROR + } + + // TODO limit the number of policies that may be executed for a single event? + + /** + * {@code True} if this object was created by this JVM instance, {@code false} + * otherwise. This will be {@code false} if this object is reconstituted from a + * persistent store or by transfer from another server. + */ + private transient boolean createdByThisJvmInstance; + + @Getter + @ToString.Include + public final String closedLoopControlName; + @Getter + @ToString.Include + private final UUID requestId; + private final ControlLoopEventContext context; + @ToString.Include + private int numOnsets = 1; + @ToString.Include + private int numAbatements = 0; + private VirtualControlLoopEvent abatement = null; + + /** + * Time, in milliseconds, when the control loop will time out. + */ + @Getter + private final long endTimeMs; + + // fields extracted from the ControlLoopParams + @Getter + private final String policyName; + private final String policyScope; + private final String policyVersion; + + private final LinkedList<ControlLoopOperation> controlLoopHistory = new LinkedList<>(); + + /** + * Maps a target entity to its lock. + */ + private final transient Map<String, LockData> target2lock = new HashMap<>(); + + private final ControlLoopProcessor processor; + private final AtomicReference<ControlLoopOperationManager2> currentOperation = new AtomicReference<>(); + + private FinalResult finalResult = null; + + @Getter + private VirtualControlLoopNotification notification; + + @Getter + private boolean updated = false; + + private final transient WorkingMemory workMem; + private transient FactHandle factHandle; + + + /** + * Constructs the object. + * + * @param params control loop parameters + * @param event event to be managed by this object + * @param workMem working memory to update if this changes + * @throws ControlLoopException if the event is invalid or if a YAML processor cannot + * be created + */ + public ControlLoopEventManager2(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem) + throws ControlLoopException { + + checkEventSyntax(event); + + if (isClosedLoopDisabled(event)) { + throw new IllegalStateException("is-closed-loop-disabled is set to true on VServer or VNF"); + } + + if (isProvStatusInactive(event)) { + throw new IllegalStateException("prov-status is not ACTIVE on VServer or VNF"); + } + + this.createdByThisJvmInstance = true; + this.closedLoopControlName = params.getClosedLoopControlName(); + this.requestId = event.getRequestId(); + this.context = new ControlLoopEventContext(event); + this.policyName = params.getPolicyName(); + this.policyScope = params.getPolicyScope(); + this.policyVersion = params.getPolicyVersion(); + this.processor = new ControlLoopProcessor(params.getToscaPolicy()); + this.workMem = workMem; + this.endTimeMs = System.currentTimeMillis() + detmControlLoopTimeoutMs(); + } + + /** + * Starts the manager. + * + * @throws ControlLoopException if the processor cannot get a policy + */ + public void start() throws ControlLoopException { + if (!isActive()) { + throw new IllegalStateException("manager is no longer active"); + } + + if ((factHandle = workMem.getFactHandle(this)) == null) { + throw new IllegalStateException("manager is not in working memory"); + } + + if (currentOperation.get() != null) { + throw new IllegalStateException("manager already started"); + } + + startOperation(); + } + + /** + * Starts an operation for the current processor policy. + * + * @throws ControlLoopException if the processor cannot get a policy + */ + private synchronized void startOperation() throws ControlLoopException { + + if ((finalResult = processor.checkIsCurrentPolicyFinal()) == null) { + // not final - start the next operation + currentOperation.set(makeOperationManager(context, processor.getCurrentPolicy())); + currentOperation.get().start(endTimeMs - System.currentTimeMillis()); + return; + } + + logger.info("final={} oper state={} for {}", finalResult, currentOperation.get().getState(), requestId); + + notification = makeNotification(); + notification.setHistory(controlLoopHistory); + + switch (finalResult) { + case FINAL_FAILURE_EXCEPTION: + notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE); + notification.setMessage("Exception in processing closed loop"); + break; + case FINAL_SUCCESS: + notification.setNotification(ControlLoopNotificationType.FINAL_SUCCESS); + break; + case FINAL_OPENLOOP: + notification.setNotification(ControlLoopNotificationType.FINAL_OPENLOOP); + break; + case FINAL_FAILURE: + default: + notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE); + break; + } + } + + /** + * Starts the next step, whatever that may be. + */ + public void nextStep() { + if (!isActive()) { + return; + } + + updated = false; + + try { + if (!currentOperation.get().nextStep()) { + // current operation is done - try the next + controlLoopHistory.addAll(currentOperation.get().getHistory()); + processor.nextPolicyForResult(currentOperation.get().getOperationResult()); + startOperation(); + } + + } catch (ControlLoopException | RuntimeException e) { + // processor problem - this is fatal + logger.warn("{}: cannot start next step for {}", closedLoopControlName, requestId, e); + notification = makeNotification(); + notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE); + notification.setMessage("Policy processing aborted due to policy error"); + notification.setHistory(controlLoopHistory); + finalResult = FinalResult.FINAL_FAILURE_EXCEPTION; + } + } + + /** + * Determines if the manager is still active. + * + * @return {@code true} if the manager is still active, {@code false} otherwise + */ + public boolean isActive() { + return (createdByThisJvmInstance && finalResult == null); + } + + /** + * Updates working memory if this changes. + * + * @param operation operation manager that was updated + */ + @Override + public synchronized void updated(ControlLoopOperationManager2 operation) { + if (!isActive() || operation != currentOperation.get()) { + // no longer working on the given operation + return; + } + + notification = makeNotification(); + + VirtualControlLoopEvent event = context.getEvent(); + + notification.setHistory(operation.getHistory()); + + switch (operation.getState()) { + case LOCK_DENIED: + notification.setNotification(ControlLoopNotificationType.REJECTED); + notification.setMessage("The target " + event.getAai().get(event.getTarget()) + " is already locked"); + break; + case LOCK_LOST: + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + notification.setMessage("The target " + event.getAai().get(event.getTarget()) + " is no longer locked"); + break; + case GUARD_STARTED: + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage( + "Sending guard query for " + operation.getActor() + " " + operation.getOperation()); + break; + case GUARD_PERMITTED: + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage("Guard result for " + operation.getActor() + " " + operation.getOperation() + + " is Permit"); + break; + case GUARD_DENIED: + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage("Guard result for " + operation.getActor() + " " + operation.getOperation() + + " is Deny"); + break; + case OPERATION_SUCCESS: + notification.setNotification(ControlLoopNotificationType.OPERATION_SUCCESS); + break; + + case CONTROL_LOOP_TIMEOUT: + logger.warn("{}: control loop timed out for {}", closedLoopControlName, requestId); + controlLoopHistory.addAll(currentOperation.get().getHistory()); + notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE); + notification.setMessage("Control Loop timed out"); + notification.setHistory(controlLoopHistory); + finalResult = FinalResult.FINAL_FAILURE; + break; + + case OPERATION_FAILURE: + default: + notification.setNotification(ControlLoopNotificationType.OPERATION_FAILURE); + break; + } + + updated = true; + workMem.update(factHandle, this); + } + + /** + * Cancels the current operation and frees all locks. + */ + public void destroy() { + ControlLoopOperationManager2 oper = currentOperation.get(); + if (oper != null) { + oper.cancel(); + } + + getBlockingExecutor().execute(this::freeAllLocks); + } + + /** + * Frees all locks. + */ + private void freeAllLocks() { + target2lock.values().forEach(LockData::free); + } + + /** + * Makes a notification message for the current operation. + * + * @return a new notification + */ + public VirtualControlLoopNotification makeNotification() { + VirtualControlLoopNotification notif = new VirtualControlLoopNotification(); + notif.setNotification(ControlLoopNotificationType.OPERATION); + notif.setFrom("policy"); + notif.setPolicyScope(policyScope); + notif.setPolicyVersion(policyVersion); + + if (finalResult == null) { + ControlLoopOperationManager2 oper = currentOperation.get(); + if (oper != null) { + notif.setMessage(oper.getOperationMessage()); + notif.setHistory(oper.getHistory()); + } + } + + return notif; + } + + /** + * An event onset/abatement. + * + * @param event the event + * @return the status + */ + public NewEventStatus onNewEvent(VirtualControlLoopEvent event) { + try { + checkEventSyntax(event); + + if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ONSET) { + if (event.equals(context.getEvent())) { + return NewEventStatus.FIRST_ONSET; + } + + numOnsets++; + return NewEventStatus.SUBSEQUENT_ONSET; + + } else { + if (abatement == null) { + abatement = event; + numAbatements++; + return NewEventStatus.FIRST_ABATEMENT; + } else { + numAbatements++; + return NewEventStatus.SUBSEQUENT_ABATEMENT; + } + } + } catch (ControlLoopException e) { + logger.error("{}: onNewEvent threw an exception", this, e); + return NewEventStatus.SYNTAX_ERROR; + } + } + + /** + * Determines the overall control loop timeout. + * + * @return the policy timeout, in milliseconds, if specified, a default timeout + * otherwise + */ + private long detmControlLoopTimeoutMs() { + // validation checks preclude null or 0 timeout values in the policy + Integer timeout = processor.getControlLoop().getTimeout(); + return TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.SECONDS); + } + + /** + * Check an event syntax. + * + * @param event the event syntax + * @throws ControlLoopException if an error occurs + */ + public void checkEventSyntax(VirtualControlLoopEvent event) throws ControlLoopException { + validateStatus(event); + if (StringUtils.isBlank(event.getClosedLoopControlName())) { + throw new ControlLoopException("No control loop name"); + } + if (event.getRequestId() == null) { + throw new ControlLoopException("No request ID"); + } + if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ABATED) { + return; + } + if (StringUtils.isBlank(event.getTarget())) { + throw new ControlLoopException("No target field"); + } else if (!VALID_TARGETS.contains(event.getTarget().toLowerCase())) { + throw new ControlLoopException("target field invalid"); + } + validateAaiData(event); + } + + private void validateStatus(VirtualControlLoopEvent event) throws ControlLoopException { + if (event.getClosedLoopEventStatus() != ControlLoopEventStatus.ONSET + && event.getClosedLoopEventStatus() != ControlLoopEventStatus.ABATED) { + throw new ControlLoopException("Invalid value in closedLoopEventStatus"); + } + } + + private void validateAaiData(VirtualControlLoopEvent event) throws ControlLoopException { + Map<String, String> eventAai = event.getAai(); + if (eventAai == null) { + throw new ControlLoopException("AAI is null"); + } + if (event.getTargetType() == null) { + throw new ControlLoopException("The Target type is null"); + } + switch (event.getTargetType()) { + case VM: + case VNF: + validateAaiVmVnfData(eventAai); + return; + case PNF: + validateAaiPnfData(eventAai); + return; + default: + throw new ControlLoopException("The target type is not supported"); + } + } + + private void validateAaiVmVnfData(Map<String, String> eventAai) throws ControlLoopException { + if (eventAai.get(GENERIC_VNF_VNF_ID) == null && eventAai.get(VSERVER_VSERVER_NAME) == null + && eventAai.get(GENERIC_VNF_VNF_NAME) == null) { + throw new ControlLoopException( + "generic-vnf.vnf-id or generic-vnf.vnf-name or vserver.vserver-name information missing"); + } + } + + private void validateAaiPnfData(Map<String, String> eventAai) throws ControlLoopException { + if (eventAai.get(PNF_NAME) == null) { + throw new ControlLoopException("AAI PNF object key pnf-name is missing"); + } + } + + /** + * Is closed loop disabled for an event. + * + * @param event the event + * @return <code>true</code> if the control loop is disabled, <code>false</code> + * otherwise + */ + public static boolean isClosedLoopDisabled(VirtualControlLoopEvent event) { + Map<String, String> aai = event.getAai(); + return (isAaiTrue(aai.get(VSERVER_IS_CLOSED_LOOP_DISABLED)) + || isAaiTrue(aai.get(GENERIC_VNF_IS_CLOSED_LOOP_DISABLED)) + || isAaiTrue(aai.get(PNF_IS_IN_MAINT))); + } + + /** + * Does provisioning status, for an event, have a value other than ACTIVE. + * + * @param event the event + * @return {@code true} if the provisioning status is neither ACTIVE nor {@code null}, + * {@code false} otherwise + */ + protected static boolean isProvStatusInactive(VirtualControlLoopEvent event) { + Map<String, String> aai = event.getAai(); + return !(PROV_STATUS_ACTIVE.equals(aai.getOrDefault(VSERVER_PROV_STATUS, PROV_STATUS_ACTIVE)) + && PROV_STATUS_ACTIVE.equals(aai.getOrDefault(GENERIC_VNF_PROV_STATUS, PROV_STATUS_ACTIVE))); + } + + /** + * Determines the boolean value represented by the given AAI field value. + * + * @param aaiValue value to be examined + * @return the boolean value represented by the field value, or {@code false} if the + * value is {@code null} + */ + protected static boolean isAaiTrue(String aaiValue) { + return (aaiValue != null && TRUE_VALUES.contains(aaiValue.toLowerCase())); + } + + /** + * Requests a lock. This requests the lock for the time that remains before the + * timeout expires. This avoids having to extend the lock. + * + * @param targetEntity entity to be locked + * @param lockUnavailableCallback function to be invoked if the lock is + * unavailable/lost + * @return a future that can be used to await the lock + */ + @Override + public synchronized CompletableFuture<OperationOutcome> requestLock(String targetEntity, + Consumer<OperationOutcome> lockUnavailableCallback) { + + long remainingMs = endTimeMs - System.currentTimeMillis(); + int remainingSec = 15 + Math.max(0, (int) TimeUnit.SECONDS.convert(remainingMs, TimeUnit.MILLISECONDS)); + + LockData data = target2lock.computeIfAbsent(targetEntity, key -> { + LockData data2 = new LockData(key, requestId); + makeLock(targetEntity, requestId.toString(), remainingSec, data2); + return data2; + }); + + data.addUnavailableCallback(lockUnavailableCallback); + + return data.getFuture(); + } + + /** + * Initializes various components, on demand. + */ + private static class LazyInitData { + private static final OperationHistoryDataManager DATA_MANAGER; + private static final ActorService ACTOR_SERVICE; + + static { + EventManagerServices services = new EventManagerServices(EVENT_MANAGER_SERVICE_CONFIG); + ACTOR_SERVICE = services.getActorService(); + DATA_MANAGER = services.getDataManager(); + } + } + + // the following methods may be overridden by junit tests + + protected ControlLoopOperationManager2 makeOperationManager(ControlLoopEventContext ctx, Policy policy) { + return new ControlLoopOperationManager2(this, ctx, policy, getExecutor()); + } + + protected Executor getExecutor() { + return ForkJoinPool.commonPool(); + } + + protected ExecutorService getBlockingExecutor() { + return PolicyEngineConstants.getManager().getExecutorService(); + } + + protected void makeLock(String targetEntity, String requestId, int holdSec, LockCallback callback) { + PolicyEngineConstants.getManager().createLock(targetEntity, requestId, holdSec, callback, false); + } + + @Override + public ActorService getActorService() { + return LazyInitData.ACTOR_SERVICE; + } + + @Override + public OperationHistoryDataManager getDataManager() { + return LazyInitData.DATA_MANAGER; + } +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2.java new file mode 100644 index 000000000..6bdaa1575 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2.java @@ -0,0 +1,684 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved. + * Modifications Copyright (C) 2019 Huawei Technologies Co., Ltd. All rights reserved. + * Modifications Copyright (C) 2019 Tech Mahindra + * Modifications Copyright (C) 2019 Bell Canada. + * ================================================================================ + * 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.eventmanager; + +import java.io.Serializable; +import java.time.Instant; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.ToString; +import org.onap.policy.aai.AaiConstants; +import org.onap.policy.aai.AaiCqResponse; +import org.onap.policy.controlloop.ControlLoopOperation; +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.PipelineUtil; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages a single Operation for a single event. Once this has been created, + * {@link #start()} should be invoked, and then {@link #nextStep()} should be invoked + * continually until it returns {@code false}, indicating that all steps have completed. + */ +@ToString(onlyExplicitlyIncluded = true) +public class ControlLoopOperationManager2 implements Serializable { + private static final long serialVersionUID = -3773199283624595410L; + private static final Logger logger = LoggerFactory.getLogger(ControlLoopOperationManager2.class); + private static final String CL_TIMEOUT_ACTOR = "-CL-TIMEOUT-"; + public static final String LOCK_ACTOR = "LOCK"; + public static final String LOCK_OPERATION = "Lock"; + private static final String GUARD_ACTOR = "GUARD"; + public static final String VSERVER_VSERVER_NAME = "vserver.vserver-name"; + public static final String GENERIC_VNF_VNF_NAME = "generic-vnf.vnf-name"; + public static final String GENERIC_VNF_VNF_ID = "generic-vnf.vnf-id"; + public static final String PNF_NAME = "pnf.pnf-name"; + + // @formatter:off + public enum State { + ACTIVE, + LOCK_DENIED, + LOCK_LOST, + GUARD_STARTED, + GUARD_PERMITTED, + GUARD_DENIED, + OPERATION_SUCCESS, + OPERATION_FAILURE, + CONTROL_LOOP_TIMEOUT + } + // @formatter:on + + private final transient ManagerContext operContext; + private final transient ControlLoopEventContext eventContext; + private final Policy policy; + + @Getter + @ToString.Include + private State state = State.ACTIVE; + + @ToString.Include + private final String requestId; + + @ToString.Include + private final String policyId; + + /** + * Bumped each time the "complete" callback is invoked by the Actor, provided it's for + * this operation. + */ + @ToString.Include + private int attempts = 0; + + private final Deque<Operation> operationHistory = new ConcurrentLinkedDeque<>(); + + /** + * Queue of outcomes yet to be processed. Outcomes are added to this each time the + * "start" or "complete" callback is invoked. + */ + @Getter(AccessLevel.PROTECTED) + private final transient Deque<OperationOutcome> outcomes = new ConcurrentLinkedDeque<>(); + + /** + * Used to cancel the running operation. + */ + @Getter(AccessLevel.PROTECTED) + private transient CompletableFuture<OperationOutcome> future = null; + + /** + * Target entity. Determined after the lock is granted, though it may require the + * custom query to be performed first. + */ + @Getter + private String targetEntity; + + @Getter(AccessLevel.PROTECTED) + private final transient ControlLoopOperationParams params; + private final transient PipelineUtil taskUtil; + + /** + * Time when the lock was first requested. + */ + private transient AtomicReference<Instant> lockStart = new AtomicReference<>(); + + // values extracted from the policy + @Getter + private final String actor; + @Getter + private final String operation; + + + /** + * Construct an instance. + * + * @param operContext this operation's context + * @param context event context + * @param policy operation's policy + * @param executor executor for the Operation + */ + public ControlLoopOperationManager2(ManagerContext operContext, ControlLoopEventContext context, Policy policy, + Executor executor) { + + this.operContext = operContext; + this.eventContext = context; + this.policy = policy; + this.requestId = context.getEvent().getRequestId().toString(); + this.policyId = "" + policy.getId(); + this.actor = policy.getActor(); + this.operation = policy.getRecipe(); + + // @formatter:off + params = ControlLoopOperationParams.builder() + .actorService(operContext.getActorService()) + .actor(actor) + .operation(operation) + .context(context) + .executor(executor) + .target(policy.getTarget()) + .startCallback(this::onStart) + .completeCallback(this::onComplete) + .build(); + // @formatter:on + + taskUtil = new PipelineUtil(params); + } + + // + // Internal class used for tracking + // + @Getter + @ToString + private class Operation implements Serializable { + private static final long serialVersionUID = 1L; + + private int attempt; + private PolicyResult policyResult; + private ControlLoopOperation clOperation; + + /** + * Constructs the object. + * + * @param outcome outcome of the operation + */ + public Operation(OperationOutcome outcome) { + attempt = ControlLoopOperationManager2.this.attempts; + policyResult = outcome.getResult(); + clOperation = outcome.toControlLoopOperation(); + } + } + + /** + * Start the operation, first acquiring any locks that are needed. This should not + * throw any exceptions, but will, instead, invoke the callbacks with exceptions. + * + * @param remainingMs time remaining, in milliseconds, for the control loop + */ + @SuppressWarnings("unchecked") + public synchronized void start(long remainingMs) { + // this is synchronized while we update "future" + + try { + // provide a default, in case something fails before requestLock() is called + lockStart.set(Instant.now()); + + // @formatter:off + future = taskUtil.sequence( + this::detmTarget, + this::requestLock, + this::startOperation); + // @formatter:on + + // handle any exceptions that may be thrown, set timeout, and handle timeout + + // @formatter:off + future.exceptionally(this::handleException) + .orTimeout(remainingMs, TimeUnit.MILLISECONDS) + .exceptionally(this::handleTimeout); + // @formatter:on + + } catch (RuntimeException e) { + handleException(e); + } + } + + /** + * Start the operation, after the lock has been acquired. + * + * @return + */ + private CompletableFuture<OperationOutcome> startOperation() { + // @formatter:off + ControlLoopOperationParams params2 = params.toBuilder() + .payload(new LinkedHashMap<>()) + .retry(policy.getRetry()) + .timeoutSec(policy.getTimeout()) + .targetEntity(targetEntity) + .build(); + // @formatter:on + + if (policy.getPayload() != null) { + params2.getPayload().putAll(policy.getPayload()); + } + + return params2.start(); + } + + /** + * Handles exceptions that may be generated. + * + * @param thrown exception that was generated + * @return {@code null} + */ + private OperationOutcome handleException(Throwable thrown) { + if (thrown instanceof CancellationException || thrown.getCause() instanceof CancellationException) { + return null; + } + + logger.warn("{}.{}: exception starting operation for {}", actor, operation, requestId, thrown); + OperationOutcome outcome = taskUtil.setOutcome(params.makeOutcome(), thrown); + outcome.setStart(lockStart.get()); + outcome.setEnd(Instant.now()); + outcome.setFinalOutcome(true); + onComplete(outcome); + + // this outcome is not used so just return "null" + return null; + } + + /** + * Handles control loop timeout exception. + * + * @param thrown exception that was generated + * @return {@code null} + */ + private OperationOutcome handleTimeout(Throwable thrown) { + logger.warn("{}.{}: control loop timeout for {}", actor, operation, requestId, thrown); + + OperationOutcome outcome = taskUtil.setOutcome(params.makeOutcome(), thrown); + outcome.setActor(CL_TIMEOUT_ACTOR); + outcome.setOperation(null); + outcome.setStart(lockStart.get()); + outcome.setEnd(Instant.now()); + outcome.setFinalOutcome(true); + onComplete(outcome); + + // cancel the operation, if it's still running + future.cancel(false); + + // this outcome is not used so just return "null" + return null; + } + + /** + * Cancels the operation. + */ + public void cancel() { + synchronized (this) { + if (future == null) { + return; + } + } + + future.cancel(false); + } + + /** + * Requests a lock on the {@link #targetEntity}. + * + * @return a future to await the lock + */ + private CompletableFuture<OperationOutcome> requestLock() { + /* + * Failures are handled via the callback, and successes are discarded by + * sequence(), without passing them to onComplete(). + * + * Return a COPY of the future so that if we try to cancel it, we'll only cancel + * the copy, not the original. This is done by tacking thenApply() onto the end. + */ + lockStart.set(Instant.now()); + return operContext.requestLock(targetEntity, this::lockUnavailable).thenApply(outcome -> outcome); + } + + /** + * Indicates that the lock on the target entity is unavailable. + * + * @param outcome lock outcome + */ + private void lockUnavailable(OperationOutcome outcome) { + + // Note: NEVER invoke onStart() for locks; only invoke onComplete() + onComplete(outcome); + + /* + * Now that we've added the lock outcome to the queue, ensure the future is + * canceled, which may, itself, generate an operation outcome. + */ + cancel(); + } + + /** + * Handles responses provided via the "start" callback. Note: this is never be invoked + * for locks; only {@link #onComplete(OperationOutcome)} is invoked for locks. + * + * @param outcome outcome provided to the callback + */ + private void onStart(OperationOutcome outcome) { + if (GUARD_ACTOR.equals(outcome.getActor())) { + addOutcome(outcome); + } + } + + /** + * Handles responses provided via the "complete" callback. Note: this is never invoked + * for "successful" locks. + * + * @param outcome outcome provided to the callback + */ + private void onComplete(OperationOutcome outcome) { + + switch (outcome.getActor()) { + case LOCK_ACTOR: + case GUARD_ACTOR: + case CL_TIMEOUT_ACTOR: + addOutcome(outcome); + break; + + default: + if (outcome.isFor(actor, operation)) { + addOutcome(outcome); + } + break; + } + } + + /** + * Adds an outcome to {@link #outcomes}. + * + * @param outcome outcome to be added + */ + private synchronized void addOutcome(OperationOutcome outcome) { + /* + * This is synchronized to prevent nextStep() from invoking processOutcome() at + * the same time. + */ + + logger.debug("added outcome={} for {}", outcome, requestId); + outcomes.add(outcome); + + if (outcomes.peekFirst() == outcomes.peekLast()) { + // this is the first outcome in the queue - process it + processOutcome(); + } + } + + /** + * Looks for the next step in the queue. + * + * @return {@code true} if more responses are expected, {@code false} otherwise + */ + public synchronized boolean nextStep() { + switch (state) { + case LOCK_DENIED: + case LOCK_LOST: + case GUARD_DENIED: + case CONTROL_LOOP_TIMEOUT: + return false; + default: + break; + } + + OperationOutcome outcome = outcomes.peek(); + if (outcome == null) { + // empty queue + return true; + } + + if (outcome.isFinalOutcome() && outcome.isFor(actor, operation)) { + return false; + } + + // first item has been processed, remove it + outcomes.remove(); + if (!outcomes.isEmpty()) { + // have a new "first" item - process it + processOutcome(); + } + + return true; + } + + /** + * Processes the first item in {@link #outcomes}. Sets the state, increments + * {@link #attempts}, if appropriate, and stores the operation history in the DB. + */ + private synchronized void processOutcome() { + OperationOutcome outcome = outcomes.peek(); + logger.debug("process outcome={} for {}", outcome, requestId); + + switch (outcome.getActor()) { + + case CL_TIMEOUT_ACTOR: + state = State.CONTROL_LOOP_TIMEOUT; + break; + + case LOCK_ACTOR: + // lock is no longer available + if (state == State.ACTIVE) { + state = State.LOCK_DENIED; + storeFailureInDataBase(outcome, PolicyResult.FAILURE_GUARD, "Operation denied by Lock"); + } else { + state = State.LOCK_LOST; + storeFailureInDataBase(outcome, PolicyResult.FAILURE, "Operation aborted by Lock"); + } + break; + + case GUARD_ACTOR: + if (outcome.getEnd() == null) { + state = State.GUARD_STARTED; + } else if (outcome.getResult() == PolicyResult.SUCCESS) { + state = State.GUARD_PERMITTED; + } else { + state = State.GUARD_DENIED; + storeFailureInDataBase(outcome, PolicyResult.FAILURE_GUARD, "Operation denied by Guard"); + } + break; + + default: + // operation completed + ++attempts; + state = (outcome.getResult() == PolicyResult.SUCCESS ? State.OPERATION_SUCCESS + : State.OPERATION_FAILURE); + operationHistory.add(new Operation(outcome)); + storeOperationInDataBase(); + break; + } + + // indicate that this has changed + operContext.updated(this); + } + + /** + * Get the operation, as a message. + * + * @return the operation, as a message + */ + public String getOperationMessage() { + Operation last = operationHistory.peekLast(); + return (last == null ? null : last.getClOperation().toMessage()); + } + + /** + * Gets the operation result. + * + * @return the operation result + */ + public PolicyResult getOperationResult() { + Operation last = operationHistory.peekLast(); + return (last == null ? PolicyResult.FAILURE_EXCEPTION : last.getPolicyResult()); + } + + /** + * Get the latest operation history. + * + * @return the latest operation history + */ + public String getOperationHistory() { + Operation last = operationHistory.peekLast(); + return (last == null ? null : last.clOperation.toHistory()); + } + + /** + * Get the history. + * + * @return the list of control loop operations + */ + public List<ControlLoopOperation> getHistory() { + return operationHistory.stream().map(Operation::getClOperation).map(ControlLoopOperation::new) + .collect(Collectors.toList()); + } + + /** + * Stores a failure in the DB. + * + * @param outcome operation outcome + * @param result result to put into the DB + * @param message message to put into the DB + */ + private void storeFailureInDataBase(OperationOutcome outcome, PolicyResult result, String message) { + outcome.setActor(actor); + outcome.setOperation(operation); + outcome.setMessage(message); + outcome.setResult(result); + + operationHistory.add(new Operation(outcome)); + storeOperationInDataBase(); + } + + /** + * Stores the latest operation in the DB. + */ + private void storeOperationInDataBase() { + operContext.getDataManager().store(requestId, eventContext.getEvent(), + operationHistory.peekLast().getClOperation()); + } + + /** + * Determines the target entity. + * + * @return a future to determine the target entity, or {@code null} if the entity has + * already been determined + */ + protected CompletableFuture<OperationOutcome> detmTarget() { + if (policy.getTarget() == null) { + throw new IllegalArgumentException("The target is null"); + } + + if (policy.getTarget().getType() == null) { + throw new IllegalArgumentException("The target type is null"); + } + + switch (policy.getTarget().getType()) { + case PNF: + return detmPnfTarget(); + case VM: + case VNF: + case VFMODULE: + return detmVfModuleTarget(); + default: + throw new IllegalArgumentException("The target type is not supported"); + } + } + + /** + * Determines the PNF target entity. + * + * @return a future to determine the target entity, or {@code null} if the entity has + * already been determined + */ + private CompletableFuture<OperationOutcome> detmPnfTarget() { + if (!PNF_NAME.equalsIgnoreCase(eventContext.getEvent().getTarget())) { + throw new IllegalArgumentException("Target does not match target type"); + } + + targetEntity = eventContext.getEnrichment().get(PNF_NAME); + if (targetEntity == null) { + throw new IllegalArgumentException("AAI section is missing " + PNF_NAME); + } + + return null; + } + + /** + * Determines the VF Module target entity. + * + * @return a future to determine the target entity, or {@code null} if the entity has + * already been determined + */ + private CompletableFuture<OperationOutcome> detmVfModuleTarget() { + String targetFieldName = eventContext.getEvent().getTarget(); + if (targetFieldName == null) { + throw new IllegalArgumentException("Target is null"); + } + + switch (targetFieldName.toLowerCase()) { + case VSERVER_VSERVER_NAME: + targetEntity = eventContext.getEnrichment().get(VSERVER_VSERVER_NAME); + break; + case GENERIC_VNF_VNF_ID: + targetEntity = eventContext.getEnrichment().get(GENERIC_VNF_VNF_ID); + break; + case GENERIC_VNF_VNF_NAME: + return detmVnfName(); + default: + throw new IllegalArgumentException("Target does not match target type"); + } + + if (targetEntity == null) { + throw new IllegalArgumentException("Enrichment data is missing " + targetFieldName); + } + + return null; + } + + /** + * Determines the VNF Name target entity. + * + * @return a future to determine the target entity, or {@code null} if the entity has + * already been determined + */ + @SuppressWarnings("unchecked") + private CompletableFuture<OperationOutcome> detmVnfName() { + // if the onset is enriched with the vnf-id, we don't need an A&AI response + targetEntity = eventContext.getEnrichment().get(GENERIC_VNF_VNF_ID); + if (targetEntity != null) { + return null; + } + + // vnf-id was not in the onset - obtain it via the custom query + + // @formatter:off + ControlLoopOperationParams cqparams = params.toBuilder() + .actor(AaiConstants.ACTOR_NAME) + .operation(AaiCqResponse.OPERATION) + .targetEntity("") + .build(); + // @formatter:on + + // perform custom query and then extract the VNF ID from it + return taskUtil.sequence(() -> eventContext.obtain(AaiCqResponse.CONTEXT_KEY, cqparams), + this::extractVnfFromCq); + } + + /** + * Extracts the VNF Name target entity from the custom query data. + * + * @return {@code null} + */ + private CompletableFuture<OperationOutcome> extractVnfFromCq() { + // already have the CQ data + AaiCqResponse cq = eventContext.getProperty(AaiCqResponse.CONTEXT_KEY); + if (cq.getDefaultGenericVnf() == null) { + throw new IllegalArgumentException("No vnf-id found"); + } + + targetEntity = cq.getDefaultGenericVnf().getVnfId(); + if (targetEntity == null) { + throw new IllegalArgumentException("No vnf-id found"); + } + + return null; + } +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/EventManagerServices.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/EventManagerServices.java new file mode 100644 index 000000000..d8668e47d --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/EventManagerServices.java @@ -0,0 +1,179 @@ +/*- + * ============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.eventmanager; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; +import lombok.Getter; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.controlloop.actor.guard.GuardActorServiceProvider; +import org.onap.policy.controlloop.actor.guard.GuardConfig; +import org.onap.policy.controlloop.actor.guard.GuardOperation; +import org.onap.policy.controlloop.actor.guard.GuardOperator; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.Util; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerImpl; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerParams; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerStub; +import org.onap.policy.controlloop.utils.ControlLoopUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Services used by the ControlLoopEventManager. + */ +@Getter +public class EventManagerServices { + public static final Logger logger = LoggerFactory.getLogger(EventManagerServices.class); + public static final String ACTOR_SERVICE_PROPERTIES = "actor.service"; + public static final String DATA_MANAGER_PROPERTIES = "operation.history"; + + public final ActorService actorService = new ActorService(); + + // assume we're using a stub until proven otherwise + public final OperationHistoryDataManager dataManager; + + /** + * Constructs the object. Configures and starts the actor service. Initializes + * {@link #dataManager}, to a "real" data manager, if guards are enabled. + * + * @param configFileName configuration file name + */ + public EventManagerServices(String configFileName) { + // configure and start actor services + Properties props = startActorService(configFileName); + + if (isGuardEnabled()) { + // guards are enabled - use a real data manager + dataManager = makeDataManager(props); + } else { + // guards are disabled - use a stub data manager + dataManager = new OperationHistoryDataManagerStub(); + } + } + + /** + * Configures and starts the actor service. + * + * @param configFileName configuration file name + * @return the properties that were loaded from the configuration file + */ + public Properties startActorService(String configFileName) { + try (InputStream inpstr = openConfigFile(configFileName)) { + Properties props = new Properties(); + props.load(inpstr); + + Map<String, Object> parameters = ControlLoopUtils.toObject(props, ACTOR_SERVICE_PROPERTIES); + ControlLoopUtils.compressLists(parameters); + + actorService.configure(parameters); + actorService.start(); + + return props; + + } catch (RuntimeException | IOException e) { + logger.error("cannot configure/start actor service"); + throw new IllegalStateException(e); + } + } + + /** + * Opens the config file. + * + * @param configFileName configuration file name + * @return the file's input stream + * @throws FileNotFoundException if the file cannot be found + */ + private InputStream openConfigFile(String configFileName) throws FileNotFoundException { + InputStream inpstr = ResourceUtils.getResourceAsStream(configFileName); + if (inpstr == null) { + throw new FileNotFoundException(configFileName); + } + + return inpstr; + } + + /** + * Determines if guards are enabled. + * + * @return {@code true} if guards are enabled, {@code false} otherwise + */ + public boolean isGuardEnabled() { + try { + GuardOperator guard = (GuardOperator) getActorService().getActor(GuardActorServiceProvider.NAME) + .getOperator(GuardOperation.NAME); + if (!guard.isConfigured()) { + logger.warn("cannot check 'disabled' property in GUARD actor - assuming disabled"); + return false; + } + + GuardConfig config = (GuardConfig) guard.getCurrentConfig(); + if (config.isDisabled()) { + logger.warn("guard disabled"); + return false; + } + + if (!guard.isAlive()) { + logger.warn("guard actor is not running"); + return false; + } + + return true; + + } catch (RuntimeException e) { + logger.warn("cannot check 'disabled' property in GUARD actor - assuming disabled", e); + return false; + } + } + + /** + * Makes and starts the data manager. + * + * @param props properties with which to configure the data manager + * @return a new data manager + */ + public OperationHistoryDataManagerImpl makeDataManager(Properties props) { + try { + Map<String, Object> parameters = ControlLoopUtils.toObject(props, DATA_MANAGER_PROPERTIES); + OperationHistoryDataManagerParams params = Util.translate(DATA_MANAGER_PROPERTIES, parameters, + OperationHistoryDataManagerParams.class); + ValidationResult result = params.validate(DATA_MANAGER_PROPERTIES); + if (!result.isValid()) { + throw new IllegalArgumentException("invalid data manager properties:\n" + result.getResult()); + } + + OperationHistoryDataManagerImpl mgr = new OperationHistoryDataManagerImpl(params); + mgr.start(); + + return mgr; + + } catch (RuntimeException e) { + logger.error("cannot start operation history data manager"); + actorService.stop(); + throw e; + } + } +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/LockData.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/LockData.java new file mode 100644 index 000000000..835600086 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/LockData.java @@ -0,0 +1,181 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2017-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.eventmanager; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.drools.core.lock.Lock; +import org.onap.policy.drools.core.lock.LockCallback; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Data for an individual lock. + */ +public class LockData implements LockCallback { + private static final Logger logger = LoggerFactory.getLogger(LockData.class); + + private final String targetEntity; + private final UUID requestId; + + /** + * Time when this was created. + */ + private final Instant createTime = Instant.now(); + + /** + * Future for obtaining the lock. Initially incomplete. + */ + private final AtomicReference<CompletableFuture<OperationOutcome>> future = + new AtomicReference<>(new CompletableFuture<>()); + + /** + * The lock. + */ + private Lock theLock = null; + + /** + * Listeners to invoke if the lock is unavailable/lost. + */ + private final List<Consumer<OperationOutcome>> unavailableCallbacks = new ArrayList<>(); + + /** + * Set to a failed outcome, if the lock becomes unavailable. + */ + private OperationOutcome failedOutcome = null; + + + /** + * Constructs the object. + * + * @param targetEntity target entity + */ + public LockData(String targetEntity, UUID requestId) { + this.targetEntity = targetEntity; + this.requestId = requestId; + } + + /** + * Gets the future to be completed when the lock operation completes. + * + * @return the lock operation future + */ + public CompletableFuture<OperationOutcome> getFuture() { + return future.get(); + } + + /** + * Adds a callback to be invoked if the lock becomes unavailable. + * + * @param callback callback to be added + */ + public void addUnavailableCallback(Consumer<OperationOutcome> callback) { + synchronized (this) { + if (failedOutcome == null) { + // hasn't failed yet - add it to the list + unavailableCallbacks.add(callback); + return; + } + } + + // already failed - invoke the callback immediately + callback.accept(failedOutcome); + } + + /** + * Frees the lock. + */ + public void free() { + Lock lock; + + synchronized (this) { + if ((lock = theLock) == null) { + return; + } + } + + lock.free(); + } + + @Override + public synchronized void lockAvailable(Lock lock) { + logger.warn("lock granted on {} for {}", targetEntity, requestId); + theLock = lock; + + OperationOutcome outcome = makeOutcome(); + outcome.setResult(PolicyResult.SUCCESS); + outcome.setMessage(ControlLoopOperation.SUCCESS_MSG); + + future.get().complete(outcome); + } + + @Override + public void lockUnavailable(Lock unused) { + synchronized (this) { + logger.warn("lock unavailable on {} for {}", targetEntity, requestId); + failedOutcome = makeOutcome(); + failedOutcome.setResult(PolicyResult.FAILURE); + failedOutcome.setMessage(ControlLoopOperation.FAILED_MSG); + } + + /* + * In case the future was already completed successfully, replace it with a failed + * future, but complete the old one, too, in case it wasn't completed yet. + */ + future.getAndSet(CompletableFuture.completedFuture(failedOutcome)).complete(failedOutcome); + + for (Consumer<OperationOutcome> callback : unavailableCallbacks) { + try { + callback.accept(new OperationOutcome(failedOutcome)); + } catch (RuntimeException e) { + logger.warn("lock callback threw an exception for {}", requestId, e); + } + } + + unavailableCallbacks.clear(); + } + + /** + * Makes a lock operation outcome. + * + * @return a new lock operation outcome + */ + private OperationOutcome makeOutcome() { + OperationOutcome outcome = new OperationOutcome(); + outcome.setActor(ControlLoopOperationManager2.LOCK_ACTOR); + outcome.setOperation(ControlLoopOperationManager2.LOCK_OPERATION); + outcome.setTarget(targetEntity); + outcome.setFinalOutcome(true); + outcome.setStart(createTime); + outcome.setEnd(Instant.now()); + + return outcome; + } +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ManagerContext.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ManagerContext.java new file mode 100644 index 000000000..0dcd30269 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/eventmanager/ManagerContext.java @@ -0,0 +1,64 @@ +/*- + * ============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.eventmanager; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; + +/** + * Context for the Operation Manager. + */ +public interface ManagerContext { + + /** + * Gets the actor service. + * + * @return the actor service + */ + ActorService getActorService(); + + /** + * Gets the operation history data manager. + * + * @return the operation history data manager + */ + OperationHistoryDataManager getDataManager(); + + /** + * Requests a lock on the specified target. + * + * @param target target to be locked + * @param lockUnavailableCallback callback to be invoked if the lock is + * unavailable/lost + * @return a future to await the lock + */ + CompletableFuture<OperationOutcome> requestLock(String target, Consumer<OperationOutcome> lockUnavailableCallback); + + /** + * Indicates that the given operation manager has been updated. + * + * @param operationMgr operation manager that has been updated + */ + void updated(ControlLoopOperationManager2 operationMgr); +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManager.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManager.java new file mode 100644 index 000000000..a1774ea6f --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManager.java @@ -0,0 +1,50 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.ophistory; + +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; + +/** + * Data manager for the Operation History table. + */ +public interface OperationHistoryDataManager { + + /** + * Stores an operation in the DB. If the queue is full, then the oldest records is + * discarded. + * + * @param requestId request ID + * @param event event with which the operation is associated + * @param operation operation to be stored + */ + void store(String requestId, VirtualControlLoopEvent event, ControlLoopOperation operation); + + /** + * Starts the background thread. + */ + public void start(); + + /** + * Stops the background thread and places an "end" item into {@link #operations}. + */ + public void stop(); +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImpl.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImpl.java new file mode 100644 index 000000000..f7576f139 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImpl.java @@ -0,0 +1,295 @@ +/*- + * ============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.ophistory; + +import java.util.Date; +import java.util.Properties; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Consumer; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.eclipse.persistence.config.PersistenceUnitProperties; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.common.utils.jpa.EntityMgrCloser; +import org.onap.policy.common.utils.jpa.EntityTransCloser; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.database.operationshistory.Dbao; +import org.onap.policy.guard.Util; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Data manager that stores records in the DB, asynchronously, using a background thread. + */ +public class OperationHistoryDataManagerImpl implements OperationHistoryDataManager { + private static final Logger logger = LoggerFactory.getLogger(OperationHistoryDataManagerImpl.class); + + /** + * Added to the end of {@link #operations} when {@link #stop()} is called. This is + * used to get the background thread out of a blocking wait for the next record. + */ + private static final Record END_MARKER = new Record(); + + // copied from the parameters + private final int maxQueueLength; + private final int batchSize; + + private final EntityManagerFactory emFactory; + + /** + * Thread that takes records from {@link #operations} and stores them in the DB. + */ + private Thread thread; + + /** + * Set to {@code true} to stop the background thread. + */ + private boolean stopped = false; + + /** + * Queue of operations waiting to be stored in the DB. When {@link #stop()} is called, + * an {@link #END_MARKER} is added to the end of the queue. + */ + private final BlockingQueue<Record> operations = new LinkedBlockingQueue<>(); + + /** + * Number of records that have been added to the DB by this data manager instance. + */ + @Getter + private long recordsAdded = 0; + + + /** + * Constructs the object. + * + * @param params data manager parameters + */ + public OperationHistoryDataManagerImpl(OperationHistoryDataManagerParams params) { + ValidationResult result = params.validate("data-manager-properties"); + if (!result.isValid()) { + throw new IllegalArgumentException(result.getResult()); + } + + this.maxQueueLength = params.getMaxQueueLength(); + this.batchSize = params.getBatchSize(); + + // create the factory using the properties + Properties props = toProperties(params); + this.emFactory = makeEntityManagerFactory(params.getPersistenceUnit(), props); + } + + @Override + public synchronized void start() { + if (stopped || thread != null) { + // already started + return; + } + + thread = makeThread(emFactory, this::run); + thread.setDaemon(true); + thread.start(); + } + + @Override + public synchronized void stop() { + stopped = true; + + if (thread == null) { + // no thread to close the factory - do it here + emFactory.close(); + + } else { + // the thread will close the factory when it sees the end marker + operations.add(END_MARKER); + } + } + + @Override + public synchronized void store(String requestId, VirtualControlLoopEvent event, ControlLoopOperation operation) { + + if (stopped) { + logger.warn("operation history thread is stopped, discarding requestId={} event={} operation={}", requestId, + event, operation); + return; + } + + operations.add(new Record(requestId, event, operation)); + + if (operations.size() > maxQueueLength) { + Record discarded = operations.remove(); + logger.warn("too many items to store in the operation history table, discarding {}", discarded); + } + } + + /** + * Takes records from {@link #operations} and stores them in the queue. Continues to + * run until {@link #stop()} is invoked, or the thread is interrupted. + * + * @param emfactory entity manager factory + */ + private void run(EntityManagerFactory emfactory) { + try { + // store records until stopped, continuing if an exception occurs + while (!stopped) { + try { + Record triple = operations.take(); + storeBatch(emfactory.createEntityManager(), triple); + + } catch (RuntimeException e) { + logger.error("failed to save data to operation history table", e); + + } catch (InterruptedException e) { + logger.error("interrupted, discarding remaining operation history data", e); + Thread.currentThread().interrupt(); + return; + } + } + + storeRemainingRecords(emfactory); + + } finally { + synchronized (this) { + stopped = true; + } + + emfactory.close(); + } + } + + /** + * Store any remaining records, but stop at the first exception. + * + * @param emfactory entity manager factory + */ + private void storeRemainingRecords(EntityManagerFactory emfactory) { + try { + while (!operations.isEmpty()) { + storeBatch(emfactory.createEntityManager(), operations.poll()); + } + + } catch (RuntimeException e) { + logger.error("failed to save remaining data to operation history table", e); + } + } + + /** + * Stores a batch of records. + * + * @param entityManager entity manager + * @param firstRecord first record to be stored + */ + private void storeBatch(EntityManager entityManager, Record firstRecord) { + + try (EntityMgrCloser emc = new EntityMgrCloser(entityManager); + EntityTransCloser trans = new EntityTransCloser(entityManager.getTransaction())) { + + int nrecords = 0; + Record record = firstRecord; + + while (record != null && record != END_MARKER) { + storeRecord(entityManager, record); + + if (++nrecords >= batchSize) { + break; + } + + record = operations.poll(); + } + + trans.commit(); + recordsAdded += nrecords; + } + } + + /** + * Stores a record. + * + * @param entityManager entity manager + * @param record record to be stored + */ + private void storeRecord(EntityManager entityMgr, Record record) { + + Dbao newEntry = new Dbao(); + + final VirtualControlLoopEvent event = record.getEvent(); + final ControlLoopOperation operation = record.getOperation(); + + newEntry.setClosedLoopName(event.getClosedLoopControlName()); + newEntry.setRequestId(record.getRequestId()); + newEntry.setActor(operation.getActor()); + newEntry.setOperation(operation.getOperation()); + newEntry.setTarget(operation.getTarget()); + newEntry.setSubrequestId(operation.getSubRequestId()); + newEntry.setMessage(operation.getMessage()); + newEntry.setOutcome(operation.getOutcome()); + if (operation.getStart() != null) { + newEntry.setStarttime(new Date(operation.getStart().toEpochMilli())); + } + if (operation.getEnd() != null) { + newEntry.setEndtime(new Date(operation.getEnd().toEpochMilli())); + } + + entityMgr.persist(newEntry); + } + + /** + * Converts the parameters to Properties. + * + * @param params parameters to be converted + * @return a new property set + */ + private Properties toProperties(OperationHistoryDataManagerParams params) { + Properties props = new Properties(); + props.put(Util.ECLIPSE_LINK_KEY_URL, params.getUrl()); + props.put(Util.ECLIPSE_LINK_KEY_USER, params.getUserName()); + props.put(Util.ECLIPSE_LINK_KEY_PASS, params.getPassword()); + props.put(PersistenceUnitProperties.CLASSLOADER, getClass().getClassLoader()); + + return props; + } + + @Getter + @NoArgsConstructor + @AllArgsConstructor + @ToString + private static class Record { + private String requestId; + private VirtualControlLoopEvent event; + private ControlLoopOperation operation; + } + + // the following may be overridden by junit tests + + protected EntityManagerFactory makeEntityManagerFactory(String opsHistPu, Properties props) { + return Persistence.createEntityManagerFactory(opsHistPu, props); + } + + protected Thread makeThread(EntityManagerFactory emfactory, Consumer<EntityManagerFactory> command) { + return new Thread(() -> command.accept(emfactory)); + } +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParams.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParams.java new file mode 100644 index 000000000..fc919d845 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParams.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.ophistory; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +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 for a Data Manager. + */ +@NotNull +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OperationHistoryDataManagerParams { + public static final String DEFAULT_PU = "OperationsHistoryPU"; + + @NotBlank + private String url; + @NotBlank + private String userName; + + // may be blank + private String password; + + @Builder.Default + private String persistenceUnit = DEFAULT_PU; + + /** + * Maximum number of records that can be waiting to be inserted into the DB. When the + * limit is reached, the oldest records are discarded. + */ + @Min(1) + @Builder.Default + private int maxQueueLength = 10000; + + /** + * Number of records to add the DB in one transaction. + */ + @Min(1) + @Builder.Default + private int batchSize = 100; + + /** + * 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/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStub.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStub.java new file mode 100644 index 000000000..e1e0cbe09 --- /dev/null +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStub.java @@ -0,0 +1,45 @@ +/*- + * ============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.ophistory; + +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; + +/** + * Stub implementation of a data manager; all methods return without doing anything. + */ +public class OperationHistoryDataManagerStub implements OperationHistoryDataManager { + + @Override + public void store(String requestId, VirtualControlLoopEvent event, ControlLoopOperation operation) { + // do nothing + } + + @Override + public void start() { + // do nothing + } + + @Override + public void stop() { + // do nothing + } +} diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/processor/ControlLoopProcessor.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/processor/ControlLoopProcessor.java index 4ef1f75c9..154462247 100644 --- a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/processor/ControlLoopProcessor.java +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/processor/ControlLoopProcessor.java @@ -57,15 +57,16 @@ public class ControlLoopProcessor implements Serializable { private final ControlLoopPolicy policy; private String currentNestedPolicyId = null; + // not serializable, thus must be transient @Getter - private ToscaPolicy toscaOpPolicy; + private transient ToscaPolicy toscaOpPolicy; @Getter private DroolsPolicy domainOpPolicy; /** * Construct an instance from yaml. - * + * * @param yaml the yaml * @throws ControlLoopException if an error occurs */ @@ -198,7 +199,7 @@ public class ControlLoopProcessor implements Serializable { /** * Get the current policy. - * + * * @return the current policy * @throws ControlLoopException if an error occurs */ @@ -217,7 +218,7 @@ public class ControlLoopProcessor implements Serializable { /** * Get the next policy given a result of the current policy. - * + * * @param result the result of the current policy * @throws ControlLoopException if an error occurs */ diff --git a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/utils/ControlLoopUtils.java b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/utils/ControlLoopUtils.java index b5d95fed7..d311b07fc 100644 --- a/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/utils/ControlLoopUtils.java +++ b/controlloop/common/eventmanager/src/main/java/org/onap/policy/controlloop/utils/ControlLoopUtils.java @@ -18,6 +18,14 @@ package org.onap.policy.controlloop.utils; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.onap.policy.controlloop.ControlLoopException; import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; import org.onap.policy.controlloop.processor.ControlLoopProcessor; @@ -31,6 +39,7 @@ import org.slf4j.LoggerFactory; public class ControlLoopUtils { public static final Logger logger = LoggerFactory.getLogger(ControlLoopUtils.class); + private static final Pattern NAME_PAT = Pattern.compile("(.*)\\[(\\d{1,3})\\]"); private ControlLoopUtils() { super(); @@ -51,4 +60,205 @@ public class ControlLoopUtils { } } + // TODO move the following to policy-common/utils + + /** + * Converts a set of properties to a Map. Supports json-path style property names with + * "." separating components, where components may have an optional subscript. + * + * @param properties properties to be converted + * @param prefix properties whose names begin with this prefix are included. The + * prefix is stripped from the name before adding the value to the map + * @return a hierarchical map representing the properties + */ + public static Map<String, Object> toObject(Properties properties, String prefix) { + String dottedPrefix = prefix + (prefix.isEmpty() || prefix.endsWith(".") ? "" : "."); + int pfxlen = dottedPrefix.length(); + + Map<String, Object> map = new LinkedHashMap<>(); + + for (String name : properties.stringPropertyNames()) { + if (name.startsWith(dottedPrefix)) { + String[] components = name.substring(pfxlen).split("[.]"); + setProperty(map, components, properties.getProperty(name)); + } + } + + return map; + } + + /** + * Sets a property within a hierarchical map. + * + * @param map map into which the value should be placed + * @param names property name components + * @param value value to be placed into the map + */ + private static void setProperty(Map<String, Object> map, String[] names, String value) { + Map<String, Object> node = map; + + final int lastComp = names.length - 1; + + // process all but the final component + for (int comp = 0; comp < lastComp; ++comp) { + node = getNode(node, names[comp]); + } + + // process the final component + String name = names[lastComp]; + Matcher matcher = NAME_PAT.matcher(name); + + if (!matcher.matches()) { + // no subscript + node.put(name, value); + return; + } + + // subscripted + List<Object> array = getArray(node, matcher.group(1)); + int index = Integer.parseInt(matcher.group(2)); + expand(array, index); + array.set(index, value); + } + + /** + * Gets a node. + * + * @param map map from which to get the object + * @param name name of the element to get from the map, with an optional subscript + * @return a Map + */ + @SuppressWarnings("unchecked") + private static Map<String, Object> getNode(Map<String, Object> map, String name) { + Matcher matcher = NAME_PAT.matcher(name); + + if (!matcher.matches()) { + // no subscript + return getObject(map, name); + } + + // subscripted + List<Object> array = getArray(map, matcher.group(1)); + int index = Integer.parseInt(matcher.group(2)); + expand(array, index); + + Object item = array.get(index); + if (item instanceof Map) { + return (Map<String, Object>) item; + + } else { + LinkedHashMap<String, Object> result = new LinkedHashMap<>(); + array.set(index, result); + return result; + } + } + + /** + * Ensures that an array's size is large enough to hold the specified element. + * + * @param array array to be expanded + * @param index index of the desired element + */ + private static void expand(List<Object> array, int index) { + while (array.size() <= index) { + array.add(null); + } + } + + /** + * Gets an object (i.e., Map) from a map. If the particular element is not a Map, then + * it is replaced with an empty Map. + * + * @param map map from which to get the object + * @param name name of the element to get from the map, without any subscript + * @return a Map + */ + private static Map<String, Object> getObject(Map<String, Object> map, String name) { + @SuppressWarnings("unchecked") + Map<String, Object> result = (Map<String, Object>) map.compute(name, (key, value) -> { + if (value instanceof Map) { + return value; + } else { + return new LinkedHashMap<>(); + } + }); + + return result; + } + + /** + * Gets an array from a map. If the particular element is not an array, then it is + * replaced with an empty array. + * + * @param map map from which to get the array + * @param name name of the element to get from the map, without any subscript + * @return an array + */ + private static List<Object> getArray(Map<String, Object> map, String name) { + @SuppressWarnings("unchecked") + List<Object> result = (List<Object>) map.compute(name, (key, value) -> { + if (value instanceof List) { + return value; + } else { + return new ArrayList<>(); + } + }); + + return result; + } + + /** + * Compresses lists contained within a generic object, removing all {@code null} + * items. + * + * @param object object to be compressed + * @return the original object, modified in place + */ + public static Object compressLists(Object object) { + if (object instanceof Map) { + @SuppressWarnings("unchecked") + Map<String, Object> asMap = (Map<String, Object>) object; + compressMapValues(asMap); + + } else if (object instanceof List) { + @SuppressWarnings("unchecked") + List<Object> asList = (List<Object>) object; + compressListItems(asList); + } + + // else: ignore anything else + + return object; + } + + /** + * Walks a hierarchical map and removes {@code null} items found in any Lists. + * + * @param map map whose lists are to be compressed + */ + private static void compressMapValues(Map<String, Object> map) { + for (Object value : map.values()) { + compressLists(value); + } + } + + /** + * Removes {@code null} items from the list. In addition, it walks the items within + * the list, compressing them, as well. + * + * @param list the list to be compressed + */ + private static void compressListItems(List<Object> list) { + Iterator<Object> iter = list.iterator(); + while (iter.hasNext()) { + Object item = iter.next(); + if (item == null) { + // null item - remove it + iter.remove(); + + } else { + compressLists(item); + } + } + } } diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java new file mode 100644 index 000000000..522d9f57b --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopEventManager2Test.java @@ -0,0 +1,812 @@ +/*- + * ============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.eventmanager; + +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.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +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.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import org.drools.core.WorkingMemory; +import org.junit.Before; +import org.junit.Test; +import org.kie.api.runtime.rule.FactHandle; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.common.utils.coder.Coder; +import org.onap.policy.common.utils.coder.CoderException; +import org.onap.policy.common.utils.coder.StandardYamlCoder; +import org.onap.policy.common.utils.io.Serializer; +import org.onap.policy.common.utils.resources.ResourceUtils; +import org.onap.policy.controlloop.ControlLoopEventStatus; +import org.onap.policy.controlloop.ControlLoopException; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.ControlLoopTargetType; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.VirtualControlLoopNotification; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; +import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager2.NewEventStatus; +import org.onap.policy.controlloop.eventmanager.ControlLoopOperationManager2.State; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.controlloop.policy.Target; +import org.onap.policy.controlloop.policy.TargetType; +import org.onap.policy.drools.core.lock.LockCallback; +import org.onap.policy.drools.core.lock.LockImpl; +import org.onap.policy.drools.core.lock.LockState; +import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; +import org.onap.policy.models.tosca.authorative.concepts.ToscaServiceTemplate; + +public class ControlLoopEventManager2Test { + private static final UUID REQ_ID = UUID.randomUUID(); + private static final String CL_NAME = "my-closed-loop-name"; + private static final String POLICY_NAME = "my-policy-name"; + private static final String POLICY_SCOPE = "my-scope"; + private static final String POLICY_VERSION = "1.2.3"; + private static final String MY_TARGET = "my-target"; + private static final String LOCK1 = "my-lock-A"; + private static final String LOCK2 = "my-lock-B"; + private static final Coder yamlCoder = new StandardYamlCoder(); + + @Mock + private WorkingMemory workMem; + @Mock + private Consumer<OperationOutcome> callback1; + @Mock + private Consumer<OperationOutcome> callback2; + @Mock + private Consumer<OperationOutcome> callback3; + @Mock + private FactHandle factHandle; + @Mock + private ActorService actors; + @Mock + private OperationHistoryDataManager dataMgr; + @Mock + private ControlLoopOperationManager2 oper1; + @Mock + private ControlLoopOperationManager2 oper2; + @Mock + private ControlLoopOperationManager2 oper3; + @Mock + private ExecutorService executor; + + private long preCreateTimeMs; + private List<LockImpl> locks; + private Target target; + private ToscaPolicy tosca; + private ControlLoopParams params; + private VirtualControlLoopEvent event; + private int updateCount; + private ControlLoopEventManager2 mgr; + + /** + * Sets up. + */ + @Before + public void setUp() throws ControlLoopException, CoderException { + MockitoAnnotations.initMocks(this); + + when(oper1.getHistory()).thenReturn(makeHistory("A")); + when(oper2.getHistory()).thenReturn(makeHistory("B")); + when(oper3.getHistory()).thenReturn(makeHistory("C")); + + when(oper1.getActor()).thenReturn("First"); + when(oper1.getOperation()).thenReturn("OperationA"); + when(oper1.getOperationMessage()).thenReturn("message-A"); + + when(oper2.getActor()).thenReturn("Second"); + when(oper2.getOperation()).thenReturn("OperationB"); + when(oper2.getOperationMessage()).thenReturn("message-B"); + + when(oper3.getActor()).thenReturn("Third"); + when(oper3.getOperation()).thenReturn("OperationC"); + when(oper3.getOperationMessage()).thenReturn("message-C"); + + when(workMem.getFactHandle(any())).thenReturn(factHandle); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.setAai(new TreeMap<>(Map.of(ControlLoopOperationManager2.VSERVER_VSERVER_NAME, MY_TARGET))); + event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET); + event.setClosedLoopControlName(CL_NAME); + event.setTargetType(TargetType.VNF.toString()); + + target = new Target(); + target.setType(TargetType.VNF); + + params = new ControlLoopParams(); + params.setClosedLoopControlName(CL_NAME); + params.setPolicyName(POLICY_NAME); + params.setPolicyScope(POLICY_SCOPE); + params.setPolicyVersion(POLICY_VERSION); + + loadPolicy("eventManager/event-mgr-simple.yaml"); + + locks = new ArrayList<>(); + + updateCount = 0; + + preCreateTimeMs = System.currentTimeMillis(); + + mgr = new MyManagerWithOper(params, event, workMem); + } + + @Test + public void testConstructor() { + assertEquals(POLICY_NAME, mgr.getPolicyName()); + + Map<String, String> orig = event.getAai(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)) + .hasMessage("is-closed-loop-disabled is set to true on VServer or VNF"); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_PROV_STATUS, "inactive")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)) + .hasMessage("prov-status is not ACTIVE on VServer or VNF"); + + // valid + event.setAai(orig); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + // invalid + event.setTarget("unknown-target"); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + /** + * Runs through a policy that has several operations. + */ + @Test + public void testMultiOperation() throws Exception { + + loadPolicy("eventManager/event-mgr-multi.yaml"); + + mgr = new MyManagerWithOper(params, event, workMem); + mgr.start(); + + for (ControlLoopOperationManager2 oper : Arrays.asList(oper1, oper2, oper3)) { + assertTrue(mgr.isActive()); + nextStep(oper, true, PolicyResult.SUCCESS); + runRule(); + + assertTrue(mgr.isActive()); + nextStep(oper, false, PolicyResult.SUCCESS); + runRule(); + } + + assertFalse(mgr.isActive()); + } + + @Test + public void testStart() throws Exception { + // start it + mgr.start(); + + // cannot re-start + assertThatCode(() -> mgr.start()).isInstanceOf(IllegalStateException.class) + .hasMessage("manager already started"); + } + + /** + * Tests start() error cases. + */ + @Test + public void testStartErrors() throws Exception { + // wrong jvm + ControlLoopEventManager2 mgr2 = new ControlLoopEventManager2(params, event, workMem); + ControlLoopEventManager2 mgr3 = Serializer.roundTrip(mgr2); + assertThatCode(() -> mgr3.start()).isInstanceOf(IllegalStateException.class) + .hasMessage("manager is no longer active"); + + // no fact handle + when(workMem.getFactHandle(any())).thenReturn(null); + assertThatCode(() -> mgr.start()).isInstanceOf(IllegalStateException.class) + .hasMessage("manager is not in working memory"); + } + + @Test + public void testNextStep_testStartOperationSuccess() throws ControlLoopException { + runOperation(PolicyResult.SUCCESS); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_SUCCESS, notif.getNotification()); + assertNull(notif.getMessage()); + + assertThatCode(() -> mgr.nextStep()).doesNotThrowAnyException(); + } + + /** + * Tests nextStep() when the next step is invalid, which should cause an exception to + * be thrown by the processor. + */ + @Test + public void testNextStepMissing() throws Exception { + mgr.start(); + + when(oper1.nextStep()).thenThrow(new IllegalArgumentException("expected exception")); + + mgr.nextStep(); + + assertFalse(mgr.isActive()); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_FAILURE, notif.getNotification()); + assertEquals("Policy processing aborted due to policy error", notif.getMessage()); + assertTrue(notif.getHistory().isEmpty()); + } + + /** + * Tests startOperation() with FINAL_FAILURE_EXCEPTION. + */ + @Test + public void testStartOperationException() throws ControlLoopException { + runOperation(PolicyResult.FAILURE_EXCEPTION); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_FAILURE, notif.getNotification()); + assertEquals("Exception in processing closed loop", notif.getMessage()); + } + + /** + * Tests startOperation() with FINAL_FAILURE. + */ + @Test + public void testStartOperationFailure() throws ControlLoopException { + runOperation(PolicyResult.FAILURE); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_FAILURE, notif.getNotification()); + assertNull(notif.getMessage()); + } + + /** + * Tests startOperation() with FINAL_OPENLOOP. + */ + @Test + public void testStartOperationOpenLoop() throws ControlLoopException { + runOperation(PolicyResult.FAILURE_GUARD); + + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(ControlLoopNotificationType.FINAL_OPENLOOP, notif.getNotification()); + assertNull(notif.getMessage()); + } + + @Test + public void testIsActive() throws Exception { + mgr = new ControlLoopEventManager2(params, event, workMem); + assertTrue(mgr.isActive()); + + ControlLoopEventManager2 mgr2 = Serializer.roundTrip(mgr); + assertFalse(mgr2.isActive()); + } + + @Test + public void testUpdated() throws ControlLoopException { + mgr.start(); + + // not the active operation - should be ignored + mgr.updated(oper3); + verify(workMem, never()).update(any(), any()); + + VirtualControlLoopNotification notif; + + // check notification data + when(oper1.getState()).thenReturn(State.LOCK_DENIED); + mgr.updated(oper1); + notif = mgr.getNotification(); + assertNotNull(notif.getHistory()); + + /* + * try the various cases + */ + when(oper1.getState()).thenReturn(State.LOCK_DENIED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.REJECTED, "The target my-target is already locked"); + + when(oper1.getState()).thenReturn(State.LOCK_LOST); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION_FAILURE, "The target my-target is no longer locked"); + + when(oper1.getState()).thenReturn(State.GUARD_STARTED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION, "Sending guard query for First OperationA"); + + when(oper1.getState()).thenReturn(State.GUARD_PERMITTED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION, "Guard result for First OperationA is Permit"); + + when(oper1.getState()).thenReturn(State.GUARD_DENIED); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION, "Guard result for First OperationA is Deny"); + + when(oper1.getState()).thenReturn(State.OPERATION_SUCCESS); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION_SUCCESS, "message-A"); + + when(oper1.getState()).thenReturn(State.OPERATION_FAILURE); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.OPERATION_FAILURE, "message-A"); + + // should still be active + assertTrue(mgr.isActive()); + + /* + * control loop time + */ + when(oper1.getState()).thenReturn(State.CONTROL_LOOP_TIMEOUT); + mgr.updated(oper1); + verifyNotification(ControlLoopNotificationType.FINAL_FAILURE, "Control Loop timed out"); + + // should now be done + assertFalse(mgr.isActive()); + } + + @Test + public void testDestroy() { + mgr.requestLock(LOCK1, callback1); + mgr.requestLock(LOCK2, callback2); + mgr.requestLock(LOCK1, callback3); + + mgr.destroy(); + + freeLocks(); + + for (LockImpl lock : locks) { + assertTrue(lock.isUnavailable()); + } + } + + /** + * Tests destroy() once it has been started. + */ + @Test + public void testDestroyStarted() throws ControlLoopException { + mgr.start(); + + mgr.requestLock(LOCK1, callback1); + mgr.requestLock(LOCK2, callback2); + mgr.requestLock(LOCK1, callback3); + + mgr.destroy(); + + freeLocks(); + + // should have canceled the operation + verify(oper1).cancel(); + + for (LockImpl lock : locks) { + assertTrue(lock.isUnavailable()); + } + } + + @Test + public void testMakeNotification() throws ControlLoopException { + mgr.start(); + + nextStep(oper1, true, PolicyResult.SUCCESS); + runRule(); + + // check notification while running + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals("message-A", notif.getMessage()); + + List<ControlLoopOperation> history = notif.getHistory(); + assertNotNull(history); + + nextStep(oper1, false, PolicyResult.SUCCESS); + runRule(); + + assertFalse(mgr.isActive()); + + // check notification when complete + notif = mgr.getNotification(); + assertNull(notif.getMessage()); + assertEquals(history, notif.getHistory()); + } + + @Test + public void testOnNewEvent() { + VirtualControlLoopEvent event2 = new VirtualControlLoopEvent(event); + assertEquals(NewEventStatus.FIRST_ONSET, mgr.onNewEvent(event2)); + + event2.setPayload("other payload"); + assertEquals(NewEventStatus.SUBSEQUENT_ONSET, mgr.onNewEvent(event2)); + assertEquals(NewEventStatus.SUBSEQUENT_ONSET, mgr.onNewEvent(event2)); + assertEquals(NewEventStatus.FIRST_ONSET, mgr.onNewEvent(event)); + + event2.setClosedLoopEventStatus(ControlLoopEventStatus.ABATED); + assertEquals(NewEventStatus.FIRST_ABATEMENT, mgr.onNewEvent(event2)); + + assertEquals(NewEventStatus.SUBSEQUENT_ABATEMENT, mgr.onNewEvent(event2)); + assertEquals(NewEventStatus.SUBSEQUENT_ABATEMENT, mgr.onNewEvent(event2)); + + event2.setClosedLoopEventStatus(null); + assertEquals(NewEventStatus.SYNTAX_ERROR, mgr.onNewEvent(event2)); + } + + @Test + public void testDetmControlLoopTimeoutMs() throws Exception { + verifyTimeout(1200 * 1000L); + } + + private void verifyTimeout(long timeMs) { + long end = mgr.getEndTimeMs(); + assertTrue(end >= preCreateTimeMs + timeMs); + assertTrue(end < preCreateTimeMs + timeMs + 5000); + } + + @Test + public void testCheckEventSyntax() { + // initially, it's valid + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setTarget("unknown-target"); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("target field invalid"); + + event.setTarget(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("No target field"); + + // abated supersedes previous errors - so it shouldn't throw an exception + event.setClosedLoopEventStatus(ControlLoopEventStatus.ABATED); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setRequestId(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("No request ID"); + + event.setClosedLoopControlName(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("No control loop name"); + } + + @Test + public void testValidateStatus() { + event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setClosedLoopEventStatus(ControlLoopEventStatus.ABATED); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setClosedLoopEventStatus(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("Invalid value in closedLoopEventStatus"); + } + + @Test + public void testValidateAaiData() { + event.setTargetType("unknown-target-type"); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("The target type is not supported"); + + event.setTargetType(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("The Target type is null"); + + event.setAai(null); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("AAI is null"); + + // VM case + event.setTargetType(ControlLoopTargetType.VM); + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class); + + // VNF case + event.setTargetType(ControlLoopTargetType.VNF); + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class); + + // PNF case + event.setTargetType(ControlLoopTargetType.PNF); + event.setAai(Map.of(ControlLoopEventManager2.PNF_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class); + } + + @Test + public void testValidateAaiVmVnfData() { + event.setTargetType(ControlLoopTargetType.VM); + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of(ControlLoopEventManager2.VSERVER_VSERVER_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of(ControlLoopEventManager2.GENERIC_VNF_VNF_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class).hasMessage( + "generic-vnf.vnf-id or generic-vnf.vnf-name or vserver.vserver-name information missing"); + } + + @Test + public void testValidateAaiPnfData() { + event.setTargetType(ControlLoopTargetType.PNF); + event.setAai(Map.of(ControlLoopEventManager2.PNF_NAME, MY_TARGET)); + assertThatCode(() -> mgr.checkEventSyntax(event)).doesNotThrowAnyException(); + + event.setAai(Map.of()); + assertThatCode(() -> mgr.checkEventSyntax(event)).isInstanceOf(ControlLoopException.class) + .hasMessage("AAI PNF object key pnf-name is missing"); + } + + @Test + public void testIsClosedLoopDisabled() { + Map<String, String> orig = event.getAai(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + + event.setAai(addAai(orig, ControlLoopEventManager2.GENERIC_VNF_IS_CLOSED_LOOP_DISABLED, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + + event.setAai(addAai(orig, ControlLoopEventManager2.PNF_IS_IN_MAINT, "true")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + private Map<String, String> addAai(Map<String, String> original, String key, String value) { + Map<String, String> map = new TreeMap<>(original); + map.put(key, value); + return map; + } + + @Test + public void testIsProvStatusInactive() { + Map<String, String> orig = event.getAai(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_PROV_STATUS, "ACTIVE")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_PROV_STATUS, "inactive")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + + event.setAai(addAai(orig, ControlLoopEventManager2.GENERIC_VNF_PROV_STATUS, "ACTIVE")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + + event.setAai(addAai(orig, ControlLoopEventManager2.GENERIC_VNF_PROV_STATUS, "inactive")); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + @Test + public void testIsAaiTrue() { + Map<String, String> orig = event.getAai(); + + for (String value : Arrays.asList("yes", "y", "true", "t", "yEs", "trUe")) { + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, value)); + assertThatThrownBy(() -> new ControlLoopEventManager2(params, event, workMem)); + } + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "false")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + + event.setAai(addAai(orig, ControlLoopEventManager2.VSERVER_IS_CLOSED_LOOP_DISABLED, "no")); + assertThatCode(() -> new ControlLoopEventManager2(params, event, workMem)).doesNotThrowAnyException(); + } + + @Test + public void testRequestLock() { + final CompletableFuture<OperationOutcome> future1 = mgr.requestLock(LOCK1, callback1); + final CompletableFuture<OperationOutcome> future2 = mgr.requestLock(LOCK2, callback2); + assertSame(future1, mgr.requestLock(LOCK1, callback3)); + + assertEquals(2, locks.size()); + + assertTrue(future1.isDone()); + assertTrue(future2.isDone()); + + verify(callback1, never()).accept(any()); + verify(callback2, never()).accept(any()); + verify(callback3, never()).accept(any()); + + // indicate that the first lock failed + locks.get(0).notifyUnavailable(); + + verify(callback1).accept(any()); + verify(callback2, never()).accept(any()); + verify(callback3).accept(any()); + } + + @Test + public void testMakeOperationManager() throws ControlLoopException { + // use a manager that creates real operation managers + mgr = new MyManager(params, event, workMem); + + assertThatCode(() -> mgr.start()).doesNotThrowAnyException(); + } + + @Test + public void testGetBlockingExecutor() throws Exception { + mgr = new ControlLoopEventManager2(params, event, workMem); + assertThatCode(() -> mgr.getBlockingExecutor()).doesNotThrowAnyException(); + } + + @Test + public void testToString() { + assertNotNull(mgr.toString()); + } + + + private void nextStep(ControlLoopOperationManager2 oper, boolean moreSteps, PolicyResult result) { + when(oper.nextStep()).thenReturn(moreSteps); + when(oper.getOperationResult()).thenReturn(result); + + if (result == PolicyResult.SUCCESS) { + when(oper.getState()).thenReturn(State.OPERATION_SUCCESS); + } else { + when(oper.getState()).thenReturn(State.OPERATION_FAILURE); + } + + mgr.updated(oper); + + updateCount++; + + verify(workMem, times(updateCount)).update(factHandle, mgr); + } + + private void runRule() { + assertTrue(mgr.isActive()); + mgr.nextStep(); + } + + private void runOperation(PolicyResult finalResult) throws ControlLoopException { + mgr.start(); + verify(oper1).start(anyLong()); + + assertTrue(mgr.isActive()); + + nextStep(oper1, true, PolicyResult.SUCCESS); + runRule(); + + nextStep(oper1, false, finalResult); + runRule(); + + assertFalse(mgr.isActive()); + + // should have no effect, because it's done + mgr.updated(oper1); + verify(workMem, times(updateCount)).update(any(), any()); + } + + private void verifyNotification(ControlLoopNotificationType expectedType, String expectedMsg) { + VirtualControlLoopNotification notif = mgr.getNotification(); + assertEquals(expectedType, notif.getNotification()); + assertEquals(expectedMsg, notif.getMessage()); + } + + private List<ControlLoopOperation> makeHistory(String message) { + ControlLoopOperation clo = new ControlLoopOperation(); + clo.setMessage("history-" + message); + + return List.of(clo); + } + + private void loadPolicy(String fileName) throws CoderException { + ToscaServiceTemplate template = + yamlCoder.decode(ResourceUtils.getResourceAsString(fileName), ToscaServiceTemplate.class); + tosca = template.getToscaTopologyTemplate().getPolicies().get(0).values().iterator().next(); + + params.setToscaPolicy(tosca); + } + + private void freeLocks() { + ArgumentCaptor<Runnable> runCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(executor).execute(runCaptor.capture()); + + runCaptor.getValue().run(); + } + + + private class MyManager extends ControlLoopEventManager2 { + private static final long serialVersionUID = 1L; + + public MyManager(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem) + throws ControlLoopException { + + super(params, event, workMem); + } + + @Override + protected ExecutorService getBlockingExecutor() { + return executor; + } + + @Override + protected void makeLock(String targetEntity, String requestId, int holdSec, LockCallback callback) { + LockImpl lock = new LockImpl(LockState.ACTIVE, targetEntity, requestId, holdSec, callback); + locks.add(lock); + callback.lockAvailable(lock); + } + + @Override + public ActorService getActorService() { + return actors; + } + + @Override + public OperationHistoryDataManager getDataManager() { + return dataMgr; + } + } + + + private class MyManagerWithOper extends MyManager { + private static final long serialVersionUID = 1L; + + public MyManagerWithOper(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem) + throws ControlLoopException { + + super(params, event, workMem); + } + + @Override + protected ControlLoopOperationManager2 makeOperationManager(ControlLoopEventContext ctx, Policy policy) { + switch (policy.getActor()) { + case "First": + return oper1; + case "Second": + return oper2; + case "Third": + return oper3; + default: + throw new IllegalArgumentException("unknown policy actor " + policy.getActor()); + } + } + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java new file mode 100644 index 000000000..a14cc1708 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/ControlLoopOperationManager2Test.java @@ -0,0 +1,936 @@ +/*- + * ============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.eventmanager; + +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.ArgumentMatchers.eq; +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.time.Instant; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.aai.domain.yang.GenericVnf; +import org.onap.policy.aai.AaiCqResponse; +import org.onap.policy.common.utils.time.PseudoExecutor; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.actor.guard.GuardActorServiceProvider; +import org.onap.policy.controlloop.actor.guard.GuardOperation; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.actorserviceprovider.Operation; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.actorserviceprovider.Operator; +import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext; +import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams; +import org.onap.policy.controlloop.actorserviceprovider.spi.Actor; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManager; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.controlloop.policy.Target; +import org.onap.policy.controlloop.policy.TargetType; + +public class ControlLoopOperationManager2Test { + private static final UUID REQ_ID = UUID.randomUUID(); + private static final String MISMATCH = "mismatch"; + private static final String POLICY_ID = "my-policy"; + private static final String POLICY_ACTOR = "my-actor"; + private static final String POLICY_OPERATION = "my-operation"; + private static final String OTHER_ACTOR = "another-actor"; + private static final String MY_TARGET = "my-target"; + private static final String MY_VNF_ID = "my-vnf-id"; + private static final String PAYLOAD_KEY = "payload-key"; + private static final String PAYLOAD_VALUE = "payload-value"; + private static final long REMAINING_MS = 5000; + private static final int MAX_RUN = 100; + private static final Integer POLICY_RETRY = 3; + private static final Integer POLICY_TIMEOUT = 20; + private static final IllegalArgumentException EXPECTED_EXCEPTION = + new IllegalArgumentException("expected exception"); + + @Captor + private ArgumentCaptor<Consumer<OperationOutcome>> lockCallback; + + @Mock + private OperationHistoryDataManager dataMgr; + @Mock + private ManagerContext mgrctx; + @Mock + private Operator policyOperator; + @Mock + private Operation policyOperation; + @Mock + private Actor policyActor; + @Mock + private ActorService actors; + @Mock + private AaiCqResponse cqdata; + @Mock + private GenericVnf vnf; + + private CompletableFuture<OperationOutcome> lockFuture; + private CompletableFuture<OperationOutcome> policyFuture; + private Target target; + private Map<String, String> payload; + private Policy policy; + private VirtualControlLoopEvent event; + private ControlLoopEventContext context; + private PseudoExecutor executor; + private ControlLoopOperationManager2 mgr; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + lockFuture = new CompletableFuture<>(); + policyFuture = new CompletableFuture<>(); + + when(mgrctx.getActorService()).thenReturn(actors); + when(mgrctx.getDataManager()).thenReturn(dataMgr); + when(mgrctx.requestLock(any(), any())).thenReturn(lockFuture); + + // configure policy operation + when(actors.getActor(POLICY_ACTOR)).thenReturn(policyActor); + when(policyActor.getOperator(POLICY_OPERATION)).thenReturn(policyOperator); + when(policyOperator.buildOperation(any())).thenReturn(policyOperation); + when(policyOperation.start()).thenReturn(policyFuture); + + when(vnf.getVnfId()).thenReturn(MY_VNF_ID); + when(cqdata.getDefaultGenericVnf()).thenReturn(vnf); + + target = new Target(); + target.setType(TargetType.VM); + + payload = Map.of(PAYLOAD_KEY, PAYLOAD_VALUE); + + policy = new Policy(); + policy.setId(POLICY_ID); + policy.setActor(POLICY_ACTOR); + policy.setRecipe(POLICY_OPERATION); + policy.setTarget(target); + policy.setPayload(payload); + policy.setRetry(POLICY_RETRY); + policy.setTimeout(POLICY_TIMEOUT); + + event = new VirtualControlLoopEvent(); + event.setRequestId(REQ_ID); + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.setAai(new TreeMap<>(Map.of(ControlLoopOperationManager2.VSERVER_VSERVER_NAME, MY_TARGET))); + + context = new ControlLoopEventContext(event); + context.setProperty(AaiCqResponse.CONTEXT_KEY, cqdata); + + executor = new PseudoExecutor(); + + mgr = new ControlLoopOperationManager2(mgrctx, context, policy, executor); + } + + @Test + public void testStart() { + mgr.start(REMAINING_MS); + + // should have determined the target entity by now + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + verify(mgrctx).requestLock(eq(MY_TARGET), any()); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + policyFuture.complete(genOpOutcome()); + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + + assertFalse(mgr.nextStep()); + + OperationOutcome outcome = mgr.getOutcomes().peek(); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + assertTrue(outcome.isFinalOutcome()); + + verify(mgrctx, times(3)).updated(mgr); + } + + /** + * Tests start() when detmTarget() (i.e., the first task) throws an exception. + */ + @Test + public void testStartDetmTargetException() { + policy.setTarget(null); + mgr.start(REMAINING_MS); + + runToCompletion(); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + } + + /** + * Tests start() when a subsequent task throws an exception. + */ + @Test + public void testStartException() { + when(policyOperation.start()).thenThrow(EXPECTED_EXCEPTION); + + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + runToCompletion(); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + } + + /** + * Tests start() when the control loop times out. + */ + @Test + public void testStartClTimeout_testHandleTimeout() throws InterruptedException { + // catch the callback when it times out + CountDownLatch updatedLatch = new CountDownLatch(1); + doAnswer(args -> { + updatedLatch.countDown(); + return null; + }).when(mgrctx).updated(any()); + + long tstart = System.currentTimeMillis(); + + // give it a short timeout + mgr.start(100); + + assertTrue(updatedLatch.await(5, TimeUnit.SECONDS)); + assertTrue(System.currentTimeMillis() - tstart >= 100); + + // don't generate any responses + runToCompletion(); + + // wait for the future to be canceled, via a background thread + CountDownLatch futureLatch = new CountDownLatch(1); + mgr.getFuture().whenComplete((unused, thrown) -> futureLatch.countDown()); + assertTrue(futureLatch.await(5, TimeUnit.SECONDS)); + + // lock should have been canceled + assertTrue(mgr.getFuture().isCancelled()); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.CONTROL_LOOP_TIMEOUT, mgr.getState()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + + // should not have tried to store anything in the DB + verify(dataMgr, never()).store(any(), any(), any()); + } + + @Test + public void testStartOperation() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + runToCompletion(); + + verify(policyOperation).start(); + + ArgumentCaptor<ControlLoopOperationParams> captor = ArgumentCaptor.forClass(ControlLoopOperationParams.class); + verify(policyOperator).buildOperation(captor.capture()); + + ControlLoopOperationParams params = captor.getValue(); + + assertNotNull(params); + assertEquals(POLICY_ACTOR, params.getActor()); + assertSame(actors, params.getActorService()); + assertNotNull(params.getCompleteCallback()); + assertSame(context, params.getContext()); + assertSame(executor, params.getExecutor()); + assertEquals(POLICY_OPERATION, params.getOperation()); + assertEquals(payload, params.getPayload()); + assertSame(REQ_ID, params.getRequestId()); + assertSame(POLICY_RETRY, params.getRetry()); + assertNotNull(params.getStartCallback()); + assertSame(target, params.getTarget()); + assertEquals(MY_TARGET, params.getTargetEntity()); + assertSame(POLICY_TIMEOUT, params.getTimeoutSec()); + } + + @Test + public void testStartOperationNullPayload() { + policy.setPayload(null); + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + runToCompletion(); + + verify(policyOperation).start(); + + ArgumentCaptor<ControlLoopOperationParams> captor = ArgumentCaptor.forClass(ControlLoopOperationParams.class); + verify(policyOperator).buildOperation(captor.capture()); + + ControlLoopOperationParams params = captor.getValue(); + + assertNotNull(params); + assertEquals(POLICY_ACTOR, params.getActor()); + assertSame(actors, params.getActorService()); + assertNotNull(params.getCompleteCallback()); + assertSame(context, params.getContext()); + assertSame(executor, params.getExecutor()); + assertEquals(POLICY_OPERATION, params.getOperation()); + assertTrue(params.getPayload().isEmpty()); + assertSame(REQ_ID, params.getRequestId()); + assertSame(POLICY_RETRY, params.getRetry()); + assertNotNull(params.getStartCallback()); + assertSame(target, params.getTarget()); + assertEquals(MY_TARGET, params.getTargetEntity()); + assertSame(POLICY_TIMEOUT, params.getTimeoutSec()); + } + + @Test + public void testGetOperationMessage() { + // no history yet + assertNull(mgr.getOperationMessage()); + + runCyle(); + assertThat(mgr.getOperationMessage()).contains("actor=my-actor").contains("operation=my-operation"); + } + + @Test + public void testGetOperationResult() { + // no history yet + assertNotNull(mgr.getOperationResult()); + + runCyle(); + assertEquals(PolicyResult.SUCCESS, mgr.getOperationResult()); + } + + /** + * Tests getOperationResult() when it ends in a failure. + */ + @Test + public void testGetOperationResultFailure() { + mgr.start(REMAINING_MS); + + genLockFailure(); + runToCompletion(); + + assertEquals(PolicyResult.FAILURE_GUARD, mgr.getOperationResult()); + } + + /** + * Tests handleException() when the exception is a "cancel". + */ + @Test + public void testHandleExceptionCanceled() { + lockFuture.cancel(false); + + mgr.start(REMAINING_MS); + + runToCompletion(); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.ACTIVE, mgr.getState()); + } + + @Test + public void testCancel() { + mgr.start(REMAINING_MS); + + mgr.cancel(); + assertTrue(mgr.getFuture().isCancelled()); + } + + /** + * Tests cancel() when the operation hasn't been started. + */ + @Test + public void testCancelNotStarted() { + assertNull(mgr.getFuture()); + + mgr.cancel(); + assertNull(mgr.getFuture()); + } + + @Test + public void testLockUnavailable() { + mgr.start(REMAINING_MS); + + runToCompletion(); + + // lock failure outcome + final OperationOutcome outcome = genLockFailure(); + + runToCompletion(); + + assertFalse(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.LOCK_DENIED, mgr.getState()); + + assertEquals(outcome, mgr.getOutcomes().peek()); + + // should have called update() for operation-start, but not for any nextStep() + verify(mgrctx).updated(mgr); + } + + /** + * Tests onStart() and onComplete() with other actors. + */ + @Test + public void testOnStart_testOnComplete() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + + // generate failure outcome for ANOTHER actor - should be ignored + OperationOutcome outcome = mgr.getParams().makeOutcome(); + outcome.setActor(OTHER_ACTOR); + outcome.setResult(PolicyResult.FAILURE); + outcome.setStart(Instant.now()); + mgr.getParams().callbackStarted(new OperationOutcome(outcome)); + + outcome.setEnd(Instant.now()); + mgr.getParams().callbackCompleted(outcome); + + policyFuture.complete(genOpOutcome()); + runToCompletion(); + + // should not include the other actor's outcome + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + + assertFalse(mgr.nextStep()); + + assertEquals(PolicyResult.SUCCESS, mgr.getOutcomes().peek().getResult()); + + verify(mgrctx, times(3)).updated(mgr); + } + + @Test + public void testNextStep() { + mgr.start(REMAINING_MS); + + // only do the lock and the guard + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertTrue(mgr.nextStep()); + + verify(mgrctx, times(2)).updated(mgr); + } + + /** + * Tests processOutcome() when the lock is denied. + */ + @Test + public void testProcessOutcomeLockDenied() { + mgr.start(REMAINING_MS); + + // unavailable from the start => "denied" + genLockFailure(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.LOCK_DENIED, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx).updated(mgr); + + verifyDb(1, PolicyResult.FAILURE_GUARD, "Operation denied by Lock"); + } + + /** + * Tests processOutcome() when the lock is lost. + */ + @Test + public void testProcessOutcomeLockLost() { + mgr.start(REMAINING_MS); + + // indicate lock success initially + lockFuture.complete(new OperationOutcome()); + + // do the guard + genGuardOutcome(); + + // now generate a lock failure => "lost" + genLockFailure(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.LOCK_LOST, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(3)).updated(mgr); + + verifyDb(1, PolicyResult.FAILURE, "Operation aborted by Lock"); + } + + /** + * Tests processOutcome() when the guard is permitted. + */ + @Test + public void testProcessOutcomeGuardPermit() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + verify(mgrctx, times(2)).updated(mgr); + + verify(dataMgr, never()).store(any(), any(), any()); + } + + /** + * Tests processOutcome() when the guard is permitted. + */ + @Test + public void testProcessOutcomeGuardDenied() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(false); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_DENIED, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(2)).updated(mgr); + + verifyDb(1, PolicyResult.FAILURE_GUARD, "Operation denied by Guard"); + } + + /** + * Tests processOutcome() when the operation is a success. + */ + @Test + public void testProcessOutcomeOperSuccess() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + genOpOutcome(); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(3)).updated(mgr); + + verifyDb(1, PolicyResult.SUCCESS, null); + } + + /** + * Tests processOutcome() when the operation is a failure. + */ + @Test + public void testProcessOutcomeOperFailure() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + genOpOutcome(false); + + runToCompletion(); + + assertEquals(ControlLoopOperationManager2.State.GUARD_STARTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.GUARD_PERMITTED, mgr.getState()); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + verifyDb(1, PolicyResult.FAILURE, null); + + assertThat(mgr.toString()).contains("attempts=1"); + + // next failure + genOpOutcome(false); + runToCompletion(); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_FAILURE, mgr.getState()); + verifyDb(2, PolicyResult.FAILURE, null); + + assertThat(mgr.toString()).contains("attempts=2"); + + // and finally a success + genOpOutcome(); + + assertTrue(mgr.nextStep()); + assertEquals(ControlLoopOperationManager2.State.OPERATION_SUCCESS, mgr.getState()); + verifyDb(3, PolicyResult.SUCCESS, null); + + assertThat(mgr.toString()).contains("attempts=3"); + + assertFalse(mgr.nextStep()); + verify(mgrctx, times(5)).updated(mgr); + } + + @Test + public void testGetOperationHistory() { + // no history yet + assertNull(mgr.getOperationHistory()); + + runCyle(); + assertThat(mgr.getOperationHistory()).contains("actor=my-actor").contains("operation=my-operation") + .contains("outcome=Success"); + } + + @Test + public void testGetHistory() { + // no history yet + assertEquals(0, mgr.getHistory().size()); + + runCyle(); + assertEquals(1, mgr.getHistory().size()); + } + + @Test + public void testDetmTargetVm() { + target.setType(TargetType.VM); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + target.setType(TargetType.VNF); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + target.setType(TargetType.VFMODULE); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // unsupported type + target.setType(TargetType.VFC); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("The target type is not supported"); + + // null type + target.setType(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("The target type is null"); + + // null target + policy.setTarget(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("The target is null"); + } + + @Test + public void testDetmPnfTarget() { + setTargetPnf(); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // missing enrichment data + event.getAai().clear(); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("AAI section is missing " + ControlLoopOperationManager2.PNF_NAME); + + // wrong target + event.setTarget(MISMATCH); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("Target does not match target type"); + } + + @Test + public void testDetmVfModuleTarget() { + // vserver + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.VSERVER_VSERVER_NAME, MY_TARGET)); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // vnf-id + event.setTarget(ControlLoopOperationManager2.GENERIC_VNF_VNF_ID); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // wrong type + event.setTarget(MISMATCH); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("Target does not match target type"); + + // missing enrichment data + event.setTarget(ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + event.getAai().clear(); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()) + .withMessage("Enrichment data is missing " + ControlLoopOperationManager2.VSERVER_VSERVER_NAME); + + // null target + event.setTarget(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("Target is null"); + } + + @Test + public void testDetmVnfName() { + setTargetVnfName(); + assertNull(mgr.detmTarget()); + assertEquals(MY_TARGET, mgr.getTargetEntity()); + + // force it to be gotten from the CQ data + event.getAai().clear(); + assertNull(mgr.detmTarget()); + assertEquals(MY_VNF_ID, mgr.getTargetEntity()); + } + + @Test + public void testExtractVnfFromCq() { + // force it to be gotten from the CQ data + setTargetVnfName(); + event.getAai().clear(); + + // missing vnf id in CQ data + when(vnf.getVnfId()).thenReturn(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("No vnf-id found"); + + // missing default vnf in CQ data + when(cqdata.getDefaultGenericVnf()).thenReturn(null); + assertThatIllegalArgumentException().isThrownBy(() -> mgr.detmTarget()).withMessage("No vnf-id found"); + } + + @Test + public void testGetState_testGetActor_testGetOperation() { + assertEquals(ControlLoopOperationManager2.State.ACTIVE, mgr.getState()); + assertEquals(POLICY_ACTOR, mgr.getActor()); + assertEquals(POLICY_OPERATION, mgr.getOperation()); + } + + @Test + public void testToString() { + assertThat(mgr.toString()).contains("state").contains("requestId").contains("policyId").contains("attempts"); + } + + /** + * Runs a cycle, from start to completion. + */ + private void runCyle() { + mgr.start(REMAINING_MS); + + lockFuture.complete(new OperationOutcome()); + genGuardOutcome(); + genOpOutcome(); + + runToCompletion(); + + assertTrue(mgr.nextStep()); + assertTrue(mgr.nextStep()); + assertFalse(mgr.nextStep()); + } + + /** + * Runs everything until the executor queue is empty. + */ + private void runToCompletion() { + assertTrue(executor.runAll(MAX_RUN)); + } + + /** + * Generates a failure outcome for the lock, and invokes the callbacks. + * + * @return the generated outcome + */ + private OperationOutcome genLockFailure() { + OperationOutcome outcome = new OperationOutcome(); + outcome.setActor(ControlLoopOperationManager2.LOCK_ACTOR); + outcome.setOperation(ControlLoopOperationManager2.LOCK_OPERATION); + outcome.setResult(PolicyResult.FAILURE); + outcome.setStart(Instant.now()); + outcome.setEnd(Instant.now()); + outcome.setFinalOutcome(true); + + verify(mgrctx).requestLock(eq(MY_TARGET), lockCallback.capture()); + lockCallback.getValue().accept(outcome); + + lockFuture.complete(outcome); + + return outcome; + } + + /** + * Generates an outcome for the guard, and invokes the callbacks. + * + * @return the generated outcome + */ + private OperationOutcome genGuardOutcome() { + return genGuardOutcome(true); + } + + /** + * Generates an outcome for the guard, and invokes the callbacks. + * + * @param permit {@code true} if the guard should be permitted, {@code false} if + * denied + * @return the generated outcome + */ + private OperationOutcome genGuardOutcome(boolean permit) { + OperationOutcome outcome = mgr.getParams().makeOutcome(); + outcome.setActor(GuardActorServiceProvider.NAME); + outcome.setOperation(GuardOperation.NAME); + outcome.setStart(Instant.now()); + mgr.getParams().callbackStarted(new OperationOutcome(outcome)); + + if (!permit) { + outcome.setResult(PolicyResult.FAILURE); + } + + outcome.setEnd(Instant.now()); + mgr.getParams().callbackCompleted(outcome); + + return outcome; + } + + /** + * Generates an outcome for the operation, itself, and invokes the callbacks. + * + * @return the generated outcome + */ + private OperationOutcome genOpOutcome() { + return genOpOutcome(true); + } + + /** + * Generates an outcome for the operation, itself, and invokes the callbacks. + * + * @param success {@code true} if the outcome should be a success, {@code false} if a + * failure + * @return the generated outcome + */ + private OperationOutcome genOpOutcome(boolean success) { + OperationOutcome outcome = mgr.getParams().makeOutcome(); + outcome.setStart(Instant.now()); + mgr.getParams().callbackStarted(new OperationOutcome(outcome)); + + if (success) { + outcome.setFinalOutcome(true); + } else { + outcome.setResult(PolicyResult.FAILURE); + } + + outcome.setEnd(Instant.now()); + mgr.getParams().callbackCompleted(outcome); + + return outcome; + } + + /** + * Configures the data for a PNF target. + */ + private void setTargetPnf() { + event.setTarget(ControlLoopOperationManager2.PNF_NAME); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.PNF_NAME, MY_TARGET)); + + target.setType(TargetType.PNF); + } + + /** + * Configures the data for a VNF-NAME target. + */ + private void setTargetVnfName() { + event.setTarget(ControlLoopOperationManager2.GENERIC_VNF_VNF_NAME); + event.getAai().clear(); + event.getAai().putAll(Map.of(ControlLoopOperationManager2.GENERIC_VNF_VNF_ID, MY_TARGET)); + + target.setType(TargetType.VNF); + } + + private void verifyDb(int nrecords, PolicyResult expectedResult, String expectedMsg) { + ArgumentCaptor<ControlLoopOperation> captor = ArgumentCaptor.forClass(ControlLoopOperation.class); + verify(dataMgr, times(nrecords)).store(any(), any(), captor.capture()); + + ControlLoopOperation oper = captor.getValue(); + + assertEquals(expectedResult.toString(), oper.getOutcome()); + assertEquals(expectedMsg, oper.getMessage()); + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java new file mode 100644 index 000000000..b32fb4438 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/EventManagerServicesTest.java @@ -0,0 +1,120 @@ +/*- + * ============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.eventmanager; + +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Properties; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.endpoints.http.client.HttpClientFactoryInstance; +import org.onap.policy.controlloop.actorserviceprovider.ActorService; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerImpl; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerStub; +import org.onap.policy.drools.utils.PropertyUtil; + +public class EventManagerServicesTest { + private static final String FILEPFX = "eventService/"; + private static final IllegalArgumentException EXPECTED_EXCEPTION = + new IllegalArgumentException("expected exception"); + + private EventManagerServices services; + + /** + * Configures HTTP clients. + */ + @BeforeClass + public static void setUpBeforeClass() throws Exception { + // start with a clean slate + HttpClientFactoryInstance.getClientFactory().destroy(); + + Properties props = + PropertyUtil.getProperties("src/test/resources/eventService/event-svc-http-client.properties"); + HttpClientFactoryInstance.getClientFactory().build(props); + } + + @AfterClass + public static void teatDownBeforeClass() { + HttpClientFactoryInstance.getClientFactory().destroy(); + } + + @After + public void tearDown() { + closeDb(); + } + + @Test + public void testEventManagerServices_testGetActorService() { + // try with guard disabled - should use DB stub + services = new EventManagerServices(FILEPFX + "event-svc-guard-disabled.properties"); + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerStub); + assertNotNull(services.getActorService()); + + // try with guard enabled - should create a DB connection + services = new EventManagerServices(FILEPFX + "event-svc-with-db.properties"); + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerImpl); + assertNotNull(services.getActorService()); + } + + @Test + public void testStartActorService() { + // config file not found + assertThatIllegalStateException().isThrownBy(() -> new EventManagerServices("missing-config-file")); + } + + @Test + public void testIsGuardEnabled() { + // cannot check guard + services = new EventManagerServices(FILEPFX + "event-svc-no-guard-actor.properties"); + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerStub); + + // force exception when checking for guard operator + services = new EventManagerServices(FILEPFX + "event-svc-with-db.properties") { + @Override + public ActorService getActorService() { + ActorService svc = mock(ActorService.class); + when(svc.getActor(any())).thenThrow(EXPECTED_EXCEPTION); + return svc; + } + }; + assertTrue(services.getDataManager() instanceof OperationHistoryDataManagerStub); + } + + @Test + public void testMakeDataManager() { + assertThatThrownBy(() -> new EventManagerServices(FILEPFX + "event-svc-invalid-db.properties")); + } + + + private void closeDb() { + if (services != null) { + services.getDataManager().stop(); + } + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.java new file mode 100644 index 000000000..dc470e7c8 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/eventmanager/LockDataTest.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.eventmanager; + +import static org.assertj.core.api.Assertions.assertThatCode; +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.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.time.Instant; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome; +import org.onap.policy.controlloop.policy.PolicyResult; +import org.onap.policy.drools.core.lock.Lock; + +public class LockDataTest { + + private static final String ENTITY = "my-entity"; + private static final UUID REQ_ID = UUID.randomUUID(); + + @Mock + private Lock lock; + @Mock + private Consumer<OperationOutcome> callback1; + @Mock + private Consumer<OperationOutcome> callback2; + @Mock + private Consumer<OperationOutcome> callback3; + + private LockData data; + + /** + * Sets up. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + data = new LockData(ENTITY, REQ_ID); + } + + @Test + public void testGetFuture() { + CompletableFuture<OperationOutcome> future = data.getFuture(); + assertNotNull(future); + assertFalse(future.isDone()); + } + + @Test + public void testAddUnavailableCallback() { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + + data.lockAvailable(lock); + verify(callback1, never()).accept(any()); + verify(callback2, never()).accept(any()); + + data.lockUnavailable(lock); + verify(callback1).accept(any()); + verify(callback2).accept(any()); + } + + /** + * Tests addUnavailableCallback() when the lock never becomes available. + */ + @Test + public void testAddUnavailableCallbackNeverAvailable() { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + + data.lockUnavailable(lock); + verify(callback1).accept(any()); + verify(callback2).accept(any()); + + data.addUnavailableCallback(callback3); + verify(callback3).accept(any()); + } + + @Test + public void testFree() { + // no lock yet + assertThatCode(() -> data.free()).doesNotThrowAnyException(); + + // no with a lock + data.lockAvailable(lock); + data.free(); + verify(lock).free(); + } + + @Test + public void testLockAvailable() throws Exception { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + + CompletableFuture<OperationOutcome> future = data.getFuture(); + data.lockAvailable(lock); + + assertSame(future, data.getFuture()); + + assertTrue(future.isDone()); + OperationOutcome outcome = future.get(); + assertEquals(ControlLoopOperationManager2.LOCK_ACTOR, outcome.getActor()); + assertEquals(ControlLoopOperationManager2.LOCK_OPERATION, outcome.getOperation()); + assertEquals(ENTITY, outcome.getTarget()); + assertEquals(PolicyResult.SUCCESS, outcome.getResult()); + assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage()); + + Instant start = outcome.getStart(); + assertNotNull(start); + + Instant end = outcome.getEnd(); + assertNotNull(end); + assertTrue(start.compareTo(end) <= 0); + + verify(callback1, never()).accept(any()); + verify(callback2, never()).accept(any()); + } + + @Test + public void testLockUnavailable() throws Exception { + data.addUnavailableCallback(callback1); + data.addUnavailableCallback(callback2); + data.addUnavailableCallback(callback3); + + // arrange for callback2 to throw an exception + doThrow(new IllegalStateException("expected exception")).when(callback2).accept(any()); + + CompletableFuture<OperationOutcome> future = data.getFuture(); + assertNotNull(future); + data.lockUnavailable(lock); + + CompletableFuture<OperationOutcome> future2 = data.getFuture(); + assertNotNull(future2); + + assertNotSame(future, future2); + + assertTrue(future.isDone()); + OperationOutcome outcome = future.get(); + + assertTrue(future2.isDone()); + assertSame(outcome, future2.get()); + + assertEquals(ControlLoopOperationManager2.LOCK_ACTOR, outcome.getActor()); + assertEquals(ControlLoopOperationManager2.LOCK_OPERATION, outcome.getOperation()); + assertEquals(ENTITY, outcome.getTarget()); + assertEquals(PolicyResult.FAILURE, outcome.getResult()); + assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage()); + + Instant start = outcome.getStart(); + assertNotNull(start); + + Instant end = outcome.getEnd(); + assertNotNull(end); + assertTrue(start.compareTo(end) <= 0); + + verify(callback1).accept(eq(outcome)); + verify(callback2).accept(eq(outcome)); + verify(callback3).accept(eq(outcome)); + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java new file mode 100644 index 000000000..8e3c1fa9b --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerImplTest.java @@ -0,0 +1,379 @@ +/*- + * ============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.ophistory; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.time.Instant; +import java.util.Properties; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import javax.persistence.EntityManagerFactory; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerParams.OperationHistoryDataManagerParamsBuilder; + +public class OperationHistoryDataManagerImplTest { + + private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception"); + private static final String MY_TARGET = "my-target"; + private static final String REQ_ID = "my-request-id"; + private static final int BATCH_SIZE = 5; + private static final int MAX_QUEUE_LENGTH = 23; + + private static EntityManagerFactory emf; + + @Mock + private Thread thread; + + private OperationHistoryDataManagerParams params; + private Consumer<EntityManagerFactory> threadFunction; + private VirtualControlLoopEvent event; + private ControlLoopOperation operation; + private EntityManagerFactory emfSpy; + + // decremented when the thread function completes + private CountDownLatch finished; + + private OperationHistoryDataManagerImpl mgr; + + + /** + * Sets up for all tests. + */ + @BeforeClass + public static void setUpBeforeClass() { + OperationHistoryDataManagerParams params = makeBuilder().build(); + + // capture the entity manager factory for re-use + new OperationHistoryDataManagerImpl(params) { + @Override + protected EntityManagerFactory makeEntityManagerFactory(String opsHistPu, Properties props) { + emf = super.makeEntityManagerFactory(opsHistPu, props); + return emf; + } + }; + } + + /** + * Restores the environment after all tests. + */ + @AfterClass + public static void tearDownAfterClass() { + emf.close(); + } + + /** + * Sets up for an individual test. + */ + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + event = new VirtualControlLoopEvent(); + event.setRequestId(UUID.randomUUID()); + + operation = new ControlLoopOperation(); + operation.setTarget(MY_TARGET); + + threadFunction = null; + finished = new CountDownLatch(1); + + // prevent the "real" emf from being closed + emfSpy = spy(emf); + doAnswer(ans -> null).when(emfSpy).close(); + + params = makeBuilder().build(); + + mgr = new PseudoThread(); + mgr.start(); + } + + @After + public void tearDown() { + mgr.stop(); + } + + @Test + public void testConstructor() { + // use a thread and manager that haven't been started yet + thread = mock(Thread.class); + mgr = new PseudoThread(); + + // should not start the thread before start() is called + verify(thread, never()).start(); + + mgr.start(); + + // should have started the thread + verify(thread).start(); + + // invalid properties + params.setUrl(null); + assertThatCode(() -> new PseudoThread()).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("data-manager-properties"); + } + + @Test + public void testStart() { + // this should have no effect + mgr.start(); + + mgr.stop(); + + // this should also have no effect + assertThatCode(() -> mgr.start()).doesNotThrowAnyException(); + } + + @Test + public void testStore_testStop() throws InterruptedException { + // store + mgr.store(REQ_ID, event, operation); + + runThread(); + + assertEquals(1, mgr.getRecordsAdded()); + } + + /** + * Tests stop() when the manager isn't running. + */ + @Test + public void testStopNotRunning() { + // use a manager that hasn't been started yet + mgr = new PseudoThread(); + mgr.stop(); + + verify(emfSpy).close(); + } + + /** + * Tests store() when it is already stopped. + */ + @Test + public void testStoreAlreadyStopped() throws InterruptedException { + mgr.stop(); + + // store + mgr.store(REQ_ID, event, operation); + + assertEquals(0, mgr.getRecordsAdded()); + } + + /** + * Tests store() when when the queue is full. + */ + @Test + public void testStoreTooManyItems() throws InterruptedException { + final int nextra = 5; + for (int nitems = 0; nitems < MAX_QUEUE_LENGTH + nextra; ++nitems) { + mgr.store(REQ_ID, event, operation); + } + + runThread(); + + assertEquals(MAX_QUEUE_LENGTH, mgr.getRecordsAdded()); + } + + @Test + public void testRun() throws InterruptedException { + + // trigger thread shutdown when it completes this batch + when(emfSpy.createEntityManager()).thenAnswer(ans -> { + mgr.stop(); + return emf.createEntityManager(); + }); + + + mgr = new RealThread(); + mgr.start(); + + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + + waitForThread(); + + verify(emfSpy).close(); + + assertEquals(3, mgr.getRecordsAdded()); + } + + private void waitForThread() { + await().atMost(5, TimeUnit.SECONDS).until(() -> !thread.isAlive()); + } + + /** + * Tests run() when the entity manager throws an exception. + */ + @Test + public void testRunException() throws InterruptedException { + AtomicInteger count = new AtomicInteger(0); + + when(emfSpy.createEntityManager()).thenAnswer(ans -> { + if (count.incrementAndGet() == 2) { + // interrupt during one of the attempts + thread.interrupt(); + } + + // throw an exception for each record + throw EXPECTED_EXCEPTION; + }); + + + mgr = new RealThread(); + mgr.start(); + + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + mgr.store(REQ_ID, event, operation); + + waitForThread(); + + verify(emfSpy).close(); + } + + /** + * Tests storeRemainingRecords() when the entity manager throws an exception. + */ + @Test + public void testStoreRemainingRecordsException() throws InterruptedException { + // arrange to throw an exception + when(emfSpy.createEntityManager()).thenThrow(EXPECTED_EXCEPTION); + + mgr.store(REQ_ID, event, operation); + + runThread(); + } + + @Test + public void testStoreRecord() throws InterruptedException { + // no start time + mgr.store(REQ_ID, event, operation); + + // no start time + operation = new ControlLoopOperation(operation); + operation.setStart(Instant.now()); + mgr.store(REQ_ID, event, operation); + + // both start and end times + operation = new ControlLoopOperation(operation); + operation.setEnd(Instant.now()); + mgr.store(REQ_ID, event, operation); + + // only end time + operation = new ControlLoopOperation(operation); + operation.setStart(null); + mgr.store(REQ_ID, event, operation); + + runThread(); + + // all of them should have been stored + assertEquals(4, mgr.getRecordsAdded()); + } + + private void runThread() throws InterruptedException { + if (threadFunction == null) { + return; + } + + Thread thread2 = new Thread(() -> { + threadFunction.accept(emfSpy); + finished.countDown(); + }); + + thread2.setDaemon(true); + thread2.start(); + + mgr.stop(); + + assertTrue(finished.await(5, TimeUnit.SECONDS)); + } + + private static OperationHistoryDataManagerParamsBuilder makeBuilder() { + // @formatter:off + return OperationHistoryDataManagerParams.builder() + .url("jdbc:h2:mem:" + OperationHistoryDataManagerImplTest.class.getSimpleName()) + .userName("sa") + .password("") + .batchSize(BATCH_SIZE) + .maxQueueLength(MAX_QUEUE_LENGTH); + // @formatter:on + } + + /** + * Manager that uses the shared DB. + */ + private class SharedDb extends OperationHistoryDataManagerImpl { + public SharedDb() { + super(params); + } + + @Override + protected EntityManagerFactory makeEntityManagerFactory(String opsHistPu, Properties props) { + // re-use the same factory to avoid re-creating the DB for each test + return emfSpy; + } + } + + /** + * Manager that uses the shared DB and a pseudo thread. + */ + private class PseudoThread extends SharedDb { + + @Override + protected Thread makeThread(EntityManagerFactory emfactory, Consumer<EntityManagerFactory> command) { + threadFunction = command; + return thread; + } + } + + /** + * Manager that uses the shared DB and catches the thread. + */ + private class RealThread extends SharedDb { + + @Override + protected Thread makeThread(EntityManagerFactory emfactory, Consumer<EntityManagerFactory> command) { + thread = super.makeThread(emfactory, command); + return thread; + } + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.java new file mode 100644 index 000000000..aeeac4796 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerParamsTest.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.ophistory; + +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.Consumer; +import org.junit.Before; +import org.junit.Test; +import org.onap.policy.common.parameters.ValidationResult; +import org.onap.policy.controlloop.ophistory.OperationHistoryDataManagerParams.OperationHistoryDataManagerParamsBuilder; + +public class OperationHistoryDataManagerParamsTest { + private static final String CONTAINER = "my-container"; + private static final int BATCH_SIZE = 10; + private static final int MAX_QUEUE_LENGTH = 20; + private static final String MY_PASS = "my-pass"; + private static final String MY_PU = "my-pu"; + private static final String MY_URL = "my-url"; + private static final String MY_USER = "my-user"; + + private OperationHistoryDataManagerParams params; + + @Before + public void setUp() { + params = makeBuilder().build(); + } + + @Test + public void test() { + assertEquals(BATCH_SIZE, params.getBatchSize()); + assertEquals(MAX_QUEUE_LENGTH, params.getMaxQueueLength()); + assertEquals(MY_PASS, params.getPassword()); + assertEquals(OperationHistoryDataManagerParams.DEFAULT_PU, params.getPersistenceUnit()); + assertEquals(MY_URL, params.getUrl()); + assertEquals(MY_USER, params.getUserName()); + + // use specified PU + assertEquals(MY_PU, makeBuilder().persistenceUnit(MY_PU).build().getPersistenceUnit()); + } + + @Test + public void testValidate() { + assertTrue(params.validate(CONTAINER).isValid()); + + testValidateField("url", "null", params2 -> params2.setUrl(null)); + testValidateField("userName", "null", params2 -> params2.setUserName(null)); + testValidateField("password", "null", params2 -> params2.setPassword(null)); + testValidateField("persistenceUnit", "null", params2 -> params2.setPersistenceUnit(null)); + + // check edge cases + params.setBatchSize(0); + assertFalse(params.validate(CONTAINER).isValid()); + + params.setBatchSize(1); + assertTrue(params.validate(CONTAINER).isValid()); + + params.setMaxQueueLength(0); + assertFalse(params.validate(CONTAINER).isValid()); + + params.setMaxQueueLength(1); + assertTrue(params.validate(CONTAINER).isValid()); + + // blank password is ok + params.setPassword(""); + assertTrue(params.validate(CONTAINER).isValid()); + } + + private void testValidateField(String fieldName, String expected, + Consumer<OperationHistoryDataManagerParams> makeInvalid) { + + // original params should be valid + ValidationResult result = params.validate(CONTAINER); + assertTrue(fieldName, result.isValid()); + + // make invalid params + OperationHistoryDataManagerParams params2 = makeBuilder().build(); + makeInvalid.accept(params2); + result = params2.validate(CONTAINER); + assertFalse(fieldName, result.isValid()); + assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected); + } + + private OperationHistoryDataManagerParamsBuilder makeBuilder() { + // @formatter:off + return OperationHistoryDataManagerParams.builder() + .batchSize(BATCH_SIZE) + .maxQueueLength(MAX_QUEUE_LENGTH) + .password(MY_PASS) + .url(MY_URL) + .userName(MY_USER); + // @formatter:on + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java new file mode 100644 index 000000000..f4a7ff8c5 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/ophistory/OperationHistoryDataManagerStubTest.java @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.controlloop.ophistory; + +import static org.assertj.core.api.Assertions.assertThatCode; + +import org.junit.Test; + +public class OperationHistoryDataManagerStubTest { + + @Test + public void test() { + OperationHistoryDataManagerStub mgr = new OperationHistoryDataManagerStub(); + + assertThatCode(() -> mgr.store(null, null, null)).doesNotThrowAnyException(); + assertThatCode(() -> mgr.stop()).doesNotThrowAnyException(); + } +} diff --git a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java index 2e4811475..2f14954ca 100644 --- a/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java +++ b/controlloop/common/eventmanager/src/test/java/org/onap/policy/controlloop/utils/ControlLoopUtilsTest.java @@ -19,11 +19,22 @@ package org.onap.policy.controlloop.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.AbstractSet; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; import org.junit.Test; +import org.onap.policy.common.utils.coder.CoderException; import org.onap.policy.common.utils.coder.StandardCoder; import org.onap.policy.controlloop.drl.legacy.ControlLoopParams; import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicy; @@ -32,8 +43,7 @@ public class ControlLoopUtilsTest { @Test public void testToControlLoopParams() throws Exception { - String policy = - new String(Files.readAllBytes(Paths.get("src/test/resources/tosca-policy-legacy-vcpe.json"))); + String policy = Files.readString(Paths.get("src/test/resources/tosca-policy-legacy-vcpe.json")); ToscaPolicy toscaPolicy = new StandardCoder().decode(policy, ToscaPolicy.class); ControlLoopParams params = ControlLoopUtils.toControlLoopParams(toscaPolicy); @@ -42,5 +52,182 @@ public class ControlLoopUtilsTest { assertEquals(toscaPolicy.getVersion(), params.getPolicyVersion()); assertEquals(toscaPolicy.getType() + ":" + toscaPolicy.getVersion(), params.getPolicyScope()); assertSame(toscaPolicy, params.getToscaPolicy()); + + assertNull(ControlLoopUtils.toControlLoopParams(null)); + } + + @Test + public void testToObject() { + Map<String, String> map = Map.of("abc", "def", "ghi", "jkl"); + Properties props = new Properties(); + props.putAll(map); + + // with empty prefix + Map<String, Object> result = ControlLoopUtils.toObject(props, ""); + assertEquals(map, result); + + // with dotted prefix - other items skipped + map = Map.of("pfx.abc", "def", "ghi", "jkl", "pfx.mno", "pqr", "differentpfx.stu", "vwx"); + props.clear(); + props.putAll(Map.of("pfx.abc", "def", "ghi", "jkl", "pfx.mno", "pqr", "differentpfx.stu", "vwx")); + result = ControlLoopUtils.toObject(props, "pfx."); + map = Map.of("abc", "def", "mno", "pqr"); + assertEquals(map, result); + + // undotted prefix - still skips other items + result = ControlLoopUtils.toObject(props, "pfx"); + assertEquals(map, result); + } + + @Test + public void testSetProperty() { + // one, two, and three components in the name, the last two with subscripts + Map<String, Object> map = Map.of("one", "one.abc", "two.def", "two.ghi", "three.jkl.mno[0]", "three.pqr", + "three.jkl.mno[1]", "three.stu"); + Properties props = new Properties(); + props.putAll(map); + + Map<String, Object> result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + map = Map.of( + "one", "one.abc", + "two", Map.of("def", "two.ghi"), + "three", Map.of("jkl", + Map.of("mno", + List.of("three.pqr", "three.stu")))); + // @formatter:on + assertEquals(map, result); + } + + @Test + public void testGetNode() { + Map<String, Object> map = Map.of("abc[0].def", "node.ghi", "abc[0].jkl", "node.mno", "abc[1].def", "node.pqr"); + Properties props = new Properties(); + props.putAll(map); + + Map<String, Object> result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + map = Map.of( + "abc", + List.of( + Map.of("def", "node.ghi", "jkl", "node.mno"), + Map.of("def", "node.pqr") + )); + // @formatter:on + assertEquals(map, result); + + } + + @Test + public void testExpand() { + // add subscripts out of order + Properties props = makeProperties("abc[2]", "expand.def", "abc[1]", "expand.ghi"); + + Map<String, Object> result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + Map<String,Object> map = + Map.of("abc", + Arrays.asList(null, "expand.ghi", "expand.def")); + // @formatter:on + assertEquals(map, result); + + } + + @Test + public void testGetObject() { + // first value is primitive, while second is a map + Properties props = makeProperties("object.abc", "object.def", "object.abc.ghi", "object.jkl"); + + Map<String, Object> result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + Map<String,Object> map = + Map.of("object", + Map.of("abc", + Map.of("ghi", "object.jkl"))); + // @formatter:on + assertEquals(map, result); + } + + @Test + public void testGetArray() { + // first value is primitive, while second is an array + Properties props = makeProperties("array.abc", "array.def", "array.abc[0].ghi", "array.jkl"); + + Map<String, Object> result = ControlLoopUtils.toObject(props, ""); + // @formatter:off + Map<String,Object> map = + Map.of("array", + Map.of("abc", + List.of( + Map.of("ghi", "array.jkl")))); + // @formatter:on + assertEquals(map, result); + } + + @Test + @SuppressWarnings("unchecked") + public void testCompressLists() throws IOException, CoderException { + assertEquals("plain-string", ControlLoopUtils.compressLists("plain-string").toString()); + + // @formatter:off + Map<String, Object> map = + Map.of( + "cmp.abc", "cmp.def", + "cmp.ghi", + Arrays.asList(null, "cmp.list1", null, "cmp.list2", + Map.of("cmp.map", Arrays.asList("cmp.map.list1", "cmp.map1.list2", null)))); + // @formatter:on + + // the data structure needs to be modifiable, so we'll encode/decode it + StandardCoder coder = new StandardCoder(); + map = coder.decode(coder.encode(map), LinkedHashMap.class); + + ControlLoopUtils.compressLists(map); + + // @formatter:off + Map<String, Object> expected = + Map.of( + "cmp.abc", "cmp.def", + "cmp.ghi", + Arrays.asList("cmp.list1", "cmp.list2", + Map.of("cmp.map", Arrays.asList("cmp.map.list1", "cmp.map1.list2")))); + // @formatter:on + assertEquals(expected, map); + } + + /** + * Makes properties containing the specified key/value pairs. The property set returns + * names in the order listed. + * + * @return a new properties containing the specified key/value pairs + */ + private Properties makeProperties(String key1, String value1, String key2, String value2) { + // control the order in which the names are returned + List<String> keyList = List.of(key1, key2); + + Set<String> keySet = new AbstractSet<>() { + @Override + public Iterator<String> iterator() { + return keyList.iterator(); + } + + @Override + public int size() { + return 2; + } + }; + + Properties props = new Properties() { + private static final long serialVersionUID = 1L; + + @Override + public Set<String> stringPropertyNames() { + return keySet; + } + }; + + props.putAll(Map.of(key1, value1, key2, value2)); + + return props; } -}
\ No newline at end of file +} diff --git a/controlloop/common/eventmanager/src/test/resources/META-INF/persistence.xml b/controlloop/common/eventmanager/src/test/resources/META-INF/persistence.xml index 07dafecbb..4d47751bf 100644 --- a/controlloop/common/eventmanager/src/test/resources/META-INF/persistence.xml +++ b/controlloop/common/eventmanager/src/test/resources/META-INF/persistence.xml @@ -29,7 +29,7 @@ <properties>
<property name="eclipselink.ddl-generation" value="create-tables" />
- <property name="eclipselink.logging.level" value="FINE" />
+ <property name="eclipselink.logging.level" value="INFO" />
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb;DATABASE_TO_UPPER=FALSE" />
diff --git a/controlloop/common/eventmanager/src/test/resources/eventManager/event-mgr-multi.yaml b/controlloop/common/eventmanager/src/test/resources/eventManager/event-mgr-multi.yaml new file mode 100644 index 000000000..7acf02159 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventManager/event-mgr-multi.yaml @@ -0,0 +1,68 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 +topology_template: + policies: + - operational.activity: + type: onap.policies.controlloop.operational.common.Drools + type_version: 1.0.0 + version: 1.0.0 + metadata: + policy-id: operational.activity + policy-version: 1.0.0 + properties: + id: ControlLoop-event-mgr + timeout: 1200 + abatement: false + trigger: first-operation + operations: + - id: first-operation + description: First action + operation: + actor: First + operation: OperationA + target: + targetType: VNF + entityIds: + resourceID: bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38 + timeout: 300 + retries: 0 + success: second-operation + failure: final_failure + failure_timeout: final_failure_timeout + failure_retries: final_failure_retries + failure_exception: final_failure_exception + failure_guard: final_failure_guard + - id: second-operation + description: Second action + operation: + actor: Second + operation: OperationB + target: + targetType: VNF + entityIds: + resourceID: bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38 + timeout: 300 + retries: 0 + success: third-operation + failure: final_failure + failure_timeout: final_failure_timeout + failure_retries: final_failure_retries + failure_exception: final_failure_exception + failure_guard: final_failure_guard + - id: third-operation + description: Third action + operation: + actor: Third + operation: OperationC + target: + targetType: VNF + entityIds: + resourceID: bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38 + timeout: 300 + retries: 0 + success: final_success + failure: final_failure + failure_timeout: final_failure_timeout + failure_retries: final_failure_retries + failure_exception: final_failure_exception + failure_guard: final_failure_guard + controllerName: usecases
\ No newline at end of file diff --git a/controlloop/common/eventmanager/src/test/resources/eventManager/event-mgr-simple.yaml b/controlloop/common/eventmanager/src/test/resources/eventManager/event-mgr-simple.yaml new file mode 100644 index 000000000..c8b6db039 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventManager/event-mgr-simple.yaml @@ -0,0 +1,34 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 +topology_template: + policies: + - operational.activity: + type: onap.policies.controlloop.operational.common.Drools + type_version: 1.0.0 + version: 1.0.0 + metadata: + policy-id: operational.activity + policy-version: 1.0.0 + properties: + id: ControlLoop-event-mgr + timeout: 1200 + abatement: false + trigger: first-operation + operations: + - id: first-operation + description: First action + operation: + actor: First + operation: OperationA + target: + targetType: VNF + entityIds: + resourceID: bbb3cefd-01c8-413c-9bdd-2b92f9ca3d38 + timeout: 300 + retries: 0 + success: final_success + failure: final_failure + failure_timeout: final_failure_timeout + failure_retries: final_failure_retries + failure_exception: final_failure_exception + failure_guard: final_openloop + controllerName: usecases
\ No newline at end of file diff --git a/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-guard-disabled.properties b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-guard-disabled.properties new file mode 100644 index 000000000..65f6c0cc1 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-guard-disabled.properties @@ -0,0 +1,23 @@ +# +# ============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======================================================== +# + +actor.service.GUARD.disabled=true +actor.service.GUARD.clientName=guard-client +actor.service.GUARD.operations.Decision.path=decide diff --git a/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-http-client.properties b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-http-client.properties new file mode 100644 index 000000000..a563afdc3 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-http-client.properties @@ -0,0 +1,24 @@ +# +# ============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======================================================== +# + +http.client.services=guard-client +http.client.services.guard-client.host=localhost +http.client.services.guard-client.port=80 +http.client.services.guard-client.managed=true diff --git a/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-invalid-db.properties b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-invalid-db.properties new file mode 100644 index 000000000..59b0615b0 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-invalid-db.properties @@ -0,0 +1,29 @@ +# +# ============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======================================================== +# + +#actor.service.GUARD.disabled=true +actor.service.GUARD.clientName=guard-client +actor.service.GUARD.operations.Decision.path=decide + +# purposely missing the URL +#operation.history.url=jdbc:h2:mem:EventManagerServicesTest + +operation.history.userName=sa +operation.history.password= diff --git a/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-no-guard-actor.properties b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-no-guard-actor.properties new file mode 100644 index 000000000..027f824e8 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-no-guard-actor.properties @@ -0,0 +1,25 @@ +# +# ============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======================================================== +# + +# all GUARD properties are commented out on purpose + +#actor.service.GUARD.disabled=true +#actor.service.GUARD.clientName=guard-client +#actor.service.GUARD.operations.Decision.path=decide diff --git a/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-with-db.properties b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-with-db.properties new file mode 100644 index 000000000..6e003f6d6 --- /dev/null +++ b/controlloop/common/eventmanager/src/test/resources/eventService/event-svc-with-db.properties @@ -0,0 +1,27 @@ +# +# ============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======================================================== +# + +#actor.service.GUARD.disabled=true +actor.service.GUARD.clientName=guard-client +actor.service.GUARD.operations.Decision.path=decide + +operation.history.url=jdbc:h2:mem:EventManagerServicesTest +operation.history.userName=sa +operation.history.password= diff --git a/controlloop/common/feature-controlloop-frankfurt/pom.xml b/controlloop/common/feature-controlloop-frankfurt/pom.xml new file mode 100644 index 000000000..f10cd2b3a --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/pom.xml @@ -0,0 +1,113 @@ +<!-- + ============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========================================================= +--> + +<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.drools-applications.controlloop.common</groupId> + <artifactId>drools-applications-common</artifactId> + <version>1.6.0-SNAPSHOT</version> + </parent> + + <artifactId>feature-controlloop-frankfurt</artifactId> + + <description> + Load Experimental Frankfurt Control Loop Controller as a feature. + </description> + + <build> + <resources> + <resource> + <directory>src/main/feature</directory> + <filtering>true</filtering> + </resource> + <resource> + <directory>src/main/resources</directory> + </resource> + </resources> + <plugins> + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <id>zipfile</id> + <goals> + <goal>single</goal> + </goals> + <phase>package</phase> + <configuration> + <attach>true</attach> + <finalName>${project.artifactId}-${project.version}</finalName> + <descriptors> + <descriptor>src/assembly/assemble_zip.xml</descriptor> + </descriptors> + <appendAssemblyId>false</appendAssemblyId> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy-dependencies</id> + <goals> + <goal>copy-dependencies</goal> + </goals> + <phase>prepare-package</phase> + <configuration> + <outputDirectory>${project.build.directory}/assembly/lib</outputDirectory> + <overWriteReleases>false</overWriteReleases> + <overWriteSnapshots>true</overWriteSnapshots> + <overWriteIfNewer>true</overWriteIfNewer> + <useRepositoryLayout>false</useRepositoryLayout> + <addParentPoms>false</addParentPoms> + <copyPom>false</copyPom> + <includeScope>runtime</includeScope> + <excludeTransitive>true</excludeTransitive> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.onap.policy.drools-applications.controlloop.common</groupId> + <artifactId>controller-frankfurt</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.drools-pdp</groupId> + <artifactId>policy-management</artifactId> + <version>${version.policy.drools-pdp}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/controlloop/common/feature-controlloop-frankfurt/src/assembly/assemble_zip.xml b/controlloop/common/feature-controlloop-frankfurt/src/assembly/assemble_zip.xml new file mode 100644 index 000000000..bee309b63 --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/src/assembly/assemble_zip.xml @@ -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========================================================= + --> + +<!-- Defines how we build the .zip file which is our distribution. --> + +<assembly + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd"> + <id>feature-controlloop-frankfurt-package</id> + <formats> + <format>zip</format> + </formats> + + <includeBaseDirectory>false</includeBaseDirectory> + + <fileSets> + <fileSet> + <directory>target</directory> + <outputDirectory>lib/feature</outputDirectory> + <includes> + <include>feature-controlloop-frankfurt-${project.version}.jar</include> + </includes> + </fileSet> + <fileSet> + <directory>target/assembly/lib</directory> + <outputDirectory>artifacts</outputDirectory> + <includes> + <include>controller-frankfurt-${project.version}.jar</include> + </includes> + </fileSet> + <fileSet> + <directory>target/assembly/lib</directory> + <outputDirectory>lib/dependencies</outputDirectory> + <includes> + <include>*.jar</include> + </includes> + <excludes> + <exclude>controller-frankfurt-${project.version}.jar</exclude> + </excludes> + </fileSet> + <fileSet> + <directory>target/classes/config</directory> + <outputDirectory>config</outputDirectory> + <fileMode>0644</fileMode> + <excludes /> + </fileSet> + <fileSet> + <directory>src/main/feature/bin</directory> + <outputDirectory>bin</outputDirectory> + <fileMode>0755</fileMode> + <excludes /> + </fileSet> + <fileSet> + <directory>src/main/feature/db</directory> + <outputDirectory>db</outputDirectory> + <fileMode>0744</fileMode> + <excludes /> + </fileSet> + <fileSet> + <directory>src/main/feature/install</directory> + <outputDirectory>install</outputDirectory> + <fileMode>0755</fileMode> + <excludes /> + </fileSet> + </fileSets> +</assembly> diff --git a/controlloop/common/feature-controlloop-frankfurt/src/main/feature/config/frankfurt-controller.properties b/controlloop/common/feature-controlloop-frankfurt/src/main/feature/config/frankfurt-controller.properties new file mode 100644 index 000000000..2129f1b40 --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/src/main/feature/config/frankfurt-controller.properties @@ -0,0 +1,63 @@ +### +# ============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========================================================= +### + +controller.name=frankfurt + +rules.groupId=${project.groupId} +rules.artifactId=controlloop-frankfurt +rules.version=${project.version} + +dmaap.source.topics=DCAE_TOPIC,APPC-CL,APPC-LCM-WRITE,SDNR-CL-RSP +dmaap.sink.topics=APPC-CL,APPC-LCM-READ,POLICY-CL-MGT,SDNR-CL,DCAE_CL_RSP + +dmaap.source.topics.DCAE_TOPIC.events=\ + org.onap.policy.controlloop.CanonicalOnset,org.onap.policy.controlloop.CanonicalAbated +dmaap.source.topics.DCAE_TOPIC.events.org.onap.policy.controlloop.CanonicalOnset.filter=\ + [?($.closedLoopEventStatus == 'ONSET')] +dmaap.source.topics.DCAE_TOPIC.events.org.onap.policy.controlloop.CanonicalAbated.filter=\ + [?($.closedLoopEventStatus == 'ABATED')] +dmaap.source.topics.DCAE_TOPIC.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gson + +dmaap.source.topics.APPC-CL.events=org.onap.policy.appc.Response +dmaap.source.topics.APPC-CL.events.org.onap.policy.appc.Response.filter=[?($.CommonHeader && $.Status)] +dmaap.source.topics.APPC-CL.events.custom.gson=org.onap.policy.appc.util.Serialization,gsonPretty + +dmaap.source.topics.APPC-LCM-WRITE.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper +dmaap.source.topics.APPC-LCM-WRITE.events.org.onap.policy.appclcm.AppcLcmDmaapWrapper.filter=[?($.type == 'response')] +dmaap.source.topics.APPC-LCM-WRITE.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson + +dmaap.sink.topics.APPC-CL.events=org.onap.policy.appc.Request +dmaap.sink.topics.APPC-CL.events.custom.gson=org.onap.policy.appc.util.Serialization,gsonPretty + +dmaap.sink.topics.APPC-LCM-READ.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper +dmaap.sink.topics.APPC-LCM-READ.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson + +dmaap.sink.topics.POLICY-CL-MGT.events=org.onap.policy.controlloop.VirtualControlLoopNotification +dmaap.sink.topics.POLICY-CL-MGT.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty + +dmaap.sink.topics.SDNR-CL.events=org.onap.policy.sdnr.PciRequestWrapper +dmaap.sink.topics.SDNR-CL.events.custom.gson=org.onap.policy.sdnr.util.Serialization,gson + +dmaap.sink.topics.DCAE_CL_RSP.events=org.onap.policy.controlloop.ControlLoopResponse +dmaap.sink.topics.DCAE_CL_RSP.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gsonPretty + +dmaap.source.topics.SDNR-CL-RSP.events=org.onap.policy.sdnr.PciResponseWrapper +dmaap.source.topics.SDNR-CL-RSP.events.org.onap.policy.sdnr.PciResponseWrapper.filter=[?($.type == 'response')] +dmaap.source.topics.SDNR-CL-RSP.events.custom.gson=org.onap.policy.sdnr.util.Serialization,gson diff --git a/controlloop/common/feature-controlloop-frankfurt/src/main/feature/config/logback-include-frankfurt.xml b/controlloop/common/feature-controlloop-frankfurt/src/main/feature/config/logback-include-frankfurt.xml new file mode 100644 index 000000000..080fb9956 --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/src/main/feature/config/logback-include-frankfurt.xml @@ -0,0 +1,56 @@ +<!-- + ============LICENSE_START======================================================= + feature-controlloop-frankfurt + ================================================================================ + 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========================================================= + --> + +<!-- + The logger configurations in this file are for each individual controller + to have their own network logger for topic traffic. This is an extension of + logback.xml or logback-eelf.xml. + + NOTE: Each logger MUST contain the same name as the control loop's controller. +--> +<included> + + <property name="logDir" value="${POLICY_LOGS}" /> + <property name="frankfurtLog" value="frankfurt-network" /> + <property name="networkPattern" value="[%d{yyyy-MM-dd'T'HH:mm:ss.SSS+00:00, UTC}|%t]%m%n" /> + + <!-- Frankfurt Network Logging Properties --> + <appender name="FrankfurtOut" class="ch.qos.logback.core.rolling.RollingFileAppender"> + <file>${logDir}/${frankfurtLog}.log</file> + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> + <fileNamePattern>${logDir}/${frankfurtLog}.%d{yyyy-MM-dd}.%i.log.zip</fileNamePattern> + <maxFileSize>50MB</maxFileSize> + <maxHistory>30</maxHistory> + <totalSizeCap>10GB</totalSizeCap> + </rollingPolicy> + <encoder> + <pattern>${networkPattern}</pattern> + </encoder> + </appender> + + <appender name="AsyncFrankfurtOut" class="ch.qos.logback.classic.AsyncAppender"> + <appender-ref ref="FrankfurtOut" /> + </appender> + + <logger name="frankfurt" level="INFO" additivity="false"> + <appender-ref ref="AsyncFrankfurtOut" /> + </logger> + +</included> diff --git a/controlloop/common/feature-controlloop-frankfurt/src/main/java/org/onap/policy/drools/apps/controlloop/feature/frankfurt/FrankfurtFeature.java b/controlloop/common/feature-controlloop-frankfurt/src/main/java/org/onap/policy/drools/apps/controlloop/feature/frankfurt/FrankfurtFeature.java new file mode 100644 index 000000000..95d8b13ec --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/src/main/java/org/onap/policy/drools/apps/controlloop/feature/frankfurt/FrankfurtFeature.java @@ -0,0 +1,46 @@ +/* + * ============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.drools.apps.controlloop.feature.frankfurt; + +import org.onap.policy.drools.features.PolicyEngineFeatureApi; + +/** + * Frankfurt Use Cases installation as a feature saves time loading the controller at + * runtime over the usual installation from nexus. It also reduces potential for errors in + * the pom.xml generated in the brmsgw side. + * + * <p/> + * There is no impact on other components as the brmsgw etc .. they will continue + * operating as usual. + * + * <p/> + * This class will be expanded in the future for additional functionality + * + */ +public class FrankfurtFeature implements PolicyEngineFeatureApi { + + public static final int SEQNO = 1000; + + @Override + public int getSequenceNumber() { + return SEQNO; + } +} diff --git a/controlloop/common/feature-controlloop-frankfurt/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureApi b/controlloop/common/feature-controlloop-frankfurt/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureApi new file mode 100644 index 000000000..dfea34b1d --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/src/main/resources/META-INF/services/org.onap.policy.drools.features.PolicyEngineFeatureApi @@ -0,0 +1 @@ +org.onap.policy.drools.apps.controlloop.feature.frankfurt.FrankfurtFeature diff --git a/controlloop/common/feature-controlloop-frankfurt/src/test/java/org/onap/policy/drools/apps/controlloop/feature/frankfurt/FrankfurtFeatureTest.java b/controlloop/common/feature-controlloop-frankfurt/src/test/java/org/onap/policy/drools/apps/controlloop/feature/frankfurt/FrankfurtFeatureTest.java new file mode 100644 index 000000000..2cb60d742 --- /dev/null +++ b/controlloop/common/feature-controlloop-frankfurt/src/test/java/org/onap/policy/drools/apps/controlloop/feature/frankfurt/FrankfurtFeatureTest.java @@ -0,0 +1,34 @@ +/* + * ============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.drools.apps.controlloop.feature.frankfurt; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.onap.policy.drools.apps.controlloop.feature.frankfurt.FrankfurtFeature; + +public class FrankfurtFeatureTest { + + @Test + public void testGetSequenceNumber() { + assertEquals(FrankfurtFeature.SEQNO, new FrankfurtFeature().getSequenceNumber()); + } +} diff --git a/controlloop/common/feature-controlloop-management/pom.xml b/controlloop/common/feature-controlloop-management/pom.xml index 8c6821616..85981f60c 100644 --- a/controlloop/common/feature-controlloop-management/pom.xml +++ b/controlloop/common/feature-controlloop-management/pom.xml @@ -172,6 +172,11 @@ </dependency> <dependency> <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.aai</artifactId> + <version>${policy.models.version}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> <artifactId>actor.appc</artifactId> <version>${policy.models.version}</version> <exclusions> @@ -198,6 +203,11 @@ </dependency> <dependency> <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> + <artifactId>actor.guard</artifactId> + <version>${policy.models.version}</version> + </dependency> + <dependency> + <groupId>org.onap.policy.models.policy-models-interactions.model-actors</groupId> <artifactId>actor.sdnr</artifactId> <version>${policy.models.version}</version> <exclusions> diff --git a/controlloop/common/feature-controlloop-management/src/main/feature/config/AAI-http-client.properties b/controlloop/common/feature-controlloop-management/src/main/feature/config/AAI-http-client.properties new file mode 100644 index 000000000..5a5f82f4d --- /dev/null +++ b/controlloop/common/feature-controlloop-management/src/main/feature/config/AAI-http-client.properties @@ -0,0 +1,27 @@ +# ============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========================================================= + +http.client.services=AAI + +http.client.services.AAI.managed=true +http.client.services.AAI.https=true +http.client.services.AAI.host=${env:AAI_HOST} +http.client.services.AAI.port=${env:AAI_PORT} +http.client.services.AAI.username=${env:AAI_USERNAME} +http.client.services.AAI.password=${env:AAI_PASSWORD} +http.client.services.AAI.contextUriPath= diff --git a/controlloop/common/feature-controlloop-management/src/main/feature/config/GUARD-http-client.properties b/controlloop/common/feature-controlloop-management/src/main/feature/config/GUARD-http-client.properties new file mode 100644 index 000000000..e1d00bd6a --- /dev/null +++ b/controlloop/common/feature-controlloop-management/src/main/feature/config/GUARD-http-client.properties @@ -0,0 +1,27 @@ +# ============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========================================================= + +http.client.services=GUARD + +http.client.services.GUARD.managed=true +http.client.services.GUARD.https=true +http.client.services.GUARD.host=${env:PDP_HOST} +http.client.services.GUARD.port=${env:PDP_PORT} +http.client.services.GUARD.username=${env:PDP_USERNAME} +http.client.services.GUARD.password=${env:PDP_PASSWORD} +http.client.services.GUARD.contextUriPath=policy/pdpx/v1 diff --git a/controlloop/common/feature-controlloop-management/src/main/feature/config/SDNC-http-client.properties b/controlloop/common/feature-controlloop-management/src/main/feature/config/SDNC-http-client.properties new file mode 100644 index 000000000..ad11c3fa5 --- /dev/null +++ b/controlloop/common/feature-controlloop-management/src/main/feature/config/SDNC-http-client.properties @@ -0,0 +1,27 @@ +# ============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========================================================= + +http.client.services=SDNC + +http.client.services.SDNC.managed=true +http.client.services.SDNC.https=true +http.client.services.SDNC.host=${env:SDNC_HOST} +http.client.services.SDNC.port=${env:SDNC_PORT} +http.client.services.SDNC.username=${env:SDNC_USERNAME} +http.client.services.SDNC.password=${env:SDNC_PASSWORD} +http.client.services.SDNC.contextUriPath= diff --git a/controlloop/common/feature-controlloop-management/src/main/feature/config/SO-http-client.properties b/controlloop/common/feature-controlloop-management/src/main/feature/config/SO-http-client.properties new file mode 100644 index 000000000..45e77ad15 --- /dev/null +++ b/controlloop/common/feature-controlloop-management/src/main/feature/config/SO-http-client.properties @@ -0,0 +1,27 @@ +# ============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========================================================= + +http.client.services=SO + +http.client.services.SO.managed=true +http.client.services.SO.https=true +http.client.services.SO.host=${env:SO_HOST} +http.client.services.SO.port=${env:SO_PORT} +http.client.services.SO.username=${env:SO_USERNAME} +http.client.services.SO.password=${env:SO_PASSWORD} +http.client.services.SO.contextUriPath= diff --git a/controlloop/common/feature-controlloop-management/src/main/feature/config/VFC-http-client.properties b/controlloop/common/feature-controlloop-management/src/main/feature/config/VFC-http-client.properties new file mode 100644 index 000000000..28a2a75b1 --- /dev/null +++ b/controlloop/common/feature-controlloop-management/src/main/feature/config/VFC-http-client.properties @@ -0,0 +1,27 @@ +# ============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========================================================= + +http.client.services=VFC + +http.client.services.VFC.managed=true +http.client.services.VFC.https=true +http.client.services.VFC.host=${env:VFC_HOST} +http.client.services.VFC.port=${env:VFC_PORT} +http.client.services.VFC.username=${env:VFC_USERNAME} +http.client.services.VFC.password=${env:VFC_PASSWORD} +http.client.services.VFC.contextUriPath=api/nslcm/v1 diff --git a/controlloop/common/feature-controlloop-management/src/main/feature/config/event-manager.properties b/controlloop/common/feature-controlloop-management/src/main/feature/config/event-manager.properties new file mode 100644 index 000000000..d957e4051 --- /dev/null +++ b/controlloop/common/feature-controlloop-management/src/main/feature/config/event-manager.properties @@ -0,0 +1,72 @@ +# +# ============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======================================================== +# + +# DB parameters +operation.history.url=jdbc:mariadb://${env:SQL_HOST}:3306/operationshistory +operation.history.userName=${env:SQL_USER} +operation.history.password=${env:SQL_PASSWORD} + +# +# Actor parameters +# +# Note: every operation must have at least one entry, otherwise it will not be +# configured and started. Thus some of them have a "placeholder" property. +# + +actor.service.GUARD.disabled=true +actor.service.GUARD.clientName=GUARD +actor.service.GUARD.operations.Decision.path=decision + +actor.service.AAI.clientName=AAI +actor.service.AAI.operations.CustomQuery.path=aai/v16/query +actor.service.AAI.operations.Tenant.path=aai/v16/search/nodes-query + +actor.service.APPC.sinkTopic=APPC-LCM-READ +actor.service.APPC.sourceTopic=APPC-LCM-WRITE +actor.service.APPC.operations.ConfigModify.placeholder= +actor.service.APPC.operations.Migrate.placeholder= +actor.service.APPC.operations.Restart.placeholder= +actor.service.APPC.operations.Rebuild.placeholder= + +# legacy APPC - must specify sink and source for each operation +actor.service.APPC.operations.ModifyConfig.sinkTopic=APPC-CL +actor.service.APPC.operations.ModifyConfig.sourceTopic=APPC-CL + +actor.service.CDS.clientName=CDS +actor.service.CDS.grpcHost=${envd:CDS_GRPC_HOST:} +actor.service.CDS.grpcPort=${envd:CDS_GRPC_PORT:} +actor.service.CDS.grpcUsername=${envd:CDS_GRPC_USERNAME:} +actor.service.CDS.grpcPassword=${envd:CDS_GRPC_PASSWORD:} +actor.service.CDS.grpcTimeout=10 +actor.service.CDS.operations.gRPC.placeholder= + +actor.service.SDNC.clientName=SDNC +actor.service.SDNC.operations.Reroute.path= +actor.service.SDNC.operations.BandwidthOnDemand.path= + +actor.service.SO.clientName=SO +actor.service.SO.pathGet=orchestrationRequests/v5/ +actor.service.SO.maxGets=20 +actor.service.SO.waitSecGet=20 +actor.service.SO.operations.VF\ Module\ Create.path=serviceInstantiation/v7/serviceInstances +actor.service.SO.operations.VF\ Module\ Delete.path=serviceInstances/v7 + +actor.service.VFC.clientName=VFC +actor.service.VFC.operations.Restart.path= diff --git a/controlloop/common/pom.xml b/controlloop/common/pom.xml index a50e3d449..e383707c4 100644 --- a/controlloop/common/pom.xml +++ b/controlloop/common/pom.xml @@ -38,11 +38,13 @@ <module>coordination</module> <module>eventmanager</module> <module>domains</module> + <module>controller-frankfurt</module> <module>controller-usecases</module> <module>feature-controlloop-utils</module> <module>feature-controlloop-trans</module> <module>feature-controlloop-management</module> <module>feature-controlloop-amsterdam</module> + <module>feature-controlloop-frankfurt</module> <module>feature-controlloop-usecases</module> </modules> |