From 3e05cb41202145e113853392e9837abf3f6ec12c Mon Sep 17 00:00:00 2001 From: "Straubs, Ralph (rs8887)" Date: Tue, 19 Nov 2019 04:11:23 -0600 Subject: Add m2 model, including the LCM application Issue-ID: POLICY-1948 Change-Id: I18a5231d3102073c928a591c9e91b241b7093680 Signed-off-by: Straubs, Ralph (rs8887) --- controlloop/m2/base/pom.xml | 105 +++ .../main/java/org/onap/policy/m2/base/Actor.java | 52 ++ .../java/org/onap/policy/m2/base/GuardAdjunct.java | 123 ++++ .../java/org/onap/policy/m2/base/OnsetAdapter.java | 156 +++++ .../java/org/onap/policy/m2/base/Operation.java | 125 ++++ .../java/org/onap/policy/m2/base/Transaction.java | 717 +++++++++++++++++++++ .../main/java/org/onap/policy/m2/base/Util.java | 62 ++ .../onap/policy/m2/base/ActorOperationTest.java | 116 ++++ .../org/onap/policy/m2/base/GuardAdjunctTest.java | 103 +++ .../org/onap/policy/m2/base/TransactionTest.java | 230 +++++++ .../java/org/onap/policy/m2/base/UtilTest.java | 73 +++ 11 files changed, 1862 insertions(+) create mode 100644 controlloop/m2/base/pom.xml create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/OnsetAdapter.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Operation.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Transaction.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Util.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/ActorOperationTest.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/GuardAdjunctTest.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/TransactionTest.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/UtilTest.java (limited to 'controlloop/m2/base') diff --git a/controlloop/m2/base/pom.xml b/controlloop/m2/base/pom.xml new file mode 100644 index 000000000..e59025cd7 --- /dev/null +++ b/controlloop/m2/base/pom.xml @@ -0,0 +1,105 @@ + + + + + 4.0.0 + + + org.onap.policy.drools-applications.controlloop.m2 + m2 + 1.6.0-SNAPSHOT + + + base + Experimental Control Loop Model - base + + + + + org.onap.policy.drools-applications.controlloop.m2 + guard + ${project.version} + + + + org.onap.policy.drools-applications.controlloop.common + eventmanager + ${project.version} + + + + org.onap.policy.models.policy-models-interactions + model-yaml + ${policy.models.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + events + ${policy.models.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + aai + ${policy.models.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + sdc + ${policy.models.version} + + + + org.onap.policy.drools-pdp + policy-management + ${version.policy.drools-pdp} + provided + + + + org.projectlombok + lombok + provided + + + + org.powermock + powermock-api-mockito + test + + + + org.powermock + powermock-module-junit4 + test + + + + junit + junit + test + + + + diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java new file mode 100644 index 000000000..00eb181fc --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.policy.Policy; + +/** + * This is the 'Actor' interface -- objects implementing this interface + * are placed within the 'nameToActor' table within class 'Transaction'. + * All of the instances are created and inserted in the table at initialization + * time, and are located using the 'Policy.actor' field as a key. + */ +public interface Actor { + /** + * Return the name associated with this Actor. + * + * @return the name associated with this Actor (as it appears in the 'yaml') + */ + String getName(); + + /** + * Create an operation for this actor, based on the supplied policy. + * + * @param transaction the transaction the operation is running under + * @param policy the policy associated with this operation + * @param onset the initial onset event that triggered the transaction + * @param attempt this value starts at 1, and is incremented for each retry + * @return the Operation instance + */ + Operation createOperation( + Transaction transaction, Policy policy, ControlLoopEvent onset, + int attempt); +} diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java new file mode 100644 index 000000000..34397e2b9 --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java @@ -0,0 +1,123 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import java.io.Serializable; + +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.guard.GuardContext; + +/** + * This adjunct class provides a way of accessing 'GuardContext' for any + * operations that use 'guard'. It needs to be created and inserted into the + * transaction 'adjunct' list prior to creating any operations that use it. + * + *

TBD: Serialization will need to factor in the fact that 'GuardContext' + * is a shared object. + */ +public class GuardAdjunct implements Transaction.Adjunct, Serializable { + private static final long serialVersionUID = 1L; + + // the associated transaction + private Transaction transaction = null; + + // the 'GuardContext' instance + private GuardContext context = null; + + /** + * Constructor -- just in case 'getInstance()' is used to create this + * instance (in which case 'guard' will not be used). + */ + public GuardAdjunct() { + // This comment is here to keep SONAR from getting upset + } + + /** + * This method is called to create the adjunct, and insert it into the + * transaction. + * + * @param transaction the associated transaction + * @param context the GuardContext derived from the controller properties + */ + public static void create(Transaction transaction, GuardContext context) { + GuardAdjunct ga = new GuardAdjunct(); + ga.transaction = transaction; + ga.context = context; + transaction.putAdjunct(ga); + } + + /** + * Return the GuardContext instance. + * + * @return the GuardContext instance + */ + public GuardContext get() { + return context; + } + + /** + * Do an asynchronous 'guard' query, and place the result in Drools memory. + * + * @param policy the policy associated with the operation + * @param target the target in a form meaningful to 'guard' + * @param requestId the transaction's request id + * @return 'true' if we have a 'GuardContext', and a response is expected, + * 'false' if 'guard' was not used, and should be skipped + */ + public boolean asyncQuery(Policy policy, String target, String requestId) { + if (context != null) { + // note that we still return 'true' as long as we have a + // 'GuardContext', even when 'guard.disabled' is set -- as long + // as there is an asynchronous response coming + context.asyncQuery(transaction.getWorkingMemory(), + policy.getActor(), + policy.getRecipe(), + target, + requestId, + transaction.getClosedLoopControlName()); + return true; + } + return false; + } + + /** + * Create a DB entry describing this operation. + * + * @param op the history entry associated with the operation + * @param target the same target that was passed on the 'asyncQuery' call + */ + public void asyncCreateDbEntry(ControlLoopOperation op, String target) { + if (context != null) { + context.asyncCreateDbEntry( + op.getStart(), + op.getEnd(), + transaction.getClosedLoopControlName(), + op.getActor(), + op.getOperation(), + target, + transaction.getRequestId().toString(), + op.getSubRequestId(), + op.getMessage(), + op.getOutcome()); + } + } +} diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/OnsetAdapter.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/OnsetAdapter.java new file mode 100644 index 000000000..37a999f06 --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/OnsetAdapter.java @@ -0,0 +1,156 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import java.io.Serializable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.ControlLoopNotification; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * This class maps the class of an incoming ONSET message into the + * appropriate adapter. The default adapter is included here as well. + */ +public class OnsetAdapter implements Serializable { + private static final long serialVersionUID = 1L; + private static Logger logger = LoggerFactory.getLogger(OnsetAdapter.class); + + // table mapping onset message class to 'OnsetAdapter' instance + private static Map map = new ConcurrentHashMap<>(); + + /** + * This method is called to add an entry to the table. + * + * @param clazz the class of the ONSET message + * @param value an instance of 'OnsetAdapter' that should be + * associated with 'clazz' + */ + public static void register(Class clazz, OnsetAdapter value) { + // only create an entry if one doesn't already exist + map.putIfAbsent(clazz, value); + } + + /** + * Map an incoming event's class into the appropriate 'OnsetAdapter' + * to use. + * + * @param event this is the onset event + * @return an adapter appropriate for the 'event' + */ + public static OnsetAdapter get(ControlLoopEvent event) { + Class clazz = event.getClass(); + OnsetAdapter rval = map.get(clazz); + if (rval != null) { + return rval; + } + + // This algorithm below is generic, in the sense that it can be used + // to find a "best match" for any class out of a set of classes + // using the class inheritance relationships. In the general case, + // it is possible that there could be multiple best matches, but this + // can only happen if all of the matching keys are interfaces, + // except perhaps one. If there are multiple matches, + // one will be chosen "at random". + + // we need to look for the best match of 'clazz' + HashSet matches = new HashSet<>(); + Class chosenMatch = null; + synchronized (map) { + for (Class possibleMatch : map.keySet()) { + if (possibleMatch.isAssignableFrom(clazz)) { + // we have a match -- see if it is the best match + boolean add = true; + for (Class match : new ArrayList(matches)) { + if (match.isAssignableFrom(possibleMatch)) { + // 'possibleMatch' is a better match than 'match' + matches.remove(match); + } else if (possibleMatch.isAssignableFrom(match)) { + // we already have a better match + add = false; + break; + } + } + if (add) { + matches.add(possibleMatch); + } + } + } + if (!matches.isEmpty()) { + // we have at least one match + chosenMatch = matches.iterator().next(); + rval = map.get(chosenMatch); + + // add this entry back into the table -- this means we can + // now use this cached entry, and don't have to run through + // the algorithm again for this class + map.put(clazz, rval); + } + } + + if (matches.isEmpty()) { + logger.error("no matches for {}", clazz); + } else if (matches.size() != 1) { + logger.warn("multiple matches for {}: {} -- chose {}", + clazz, matches, chosenMatch); + } + + return rval; + } + + /* ============================================================ */ + + // the following code creates an initial entry in the table + private static OnsetAdapter instance = new OnsetAdapter(); + + static { + register(ControlLoopEvent.class, instance); + } + + // the new 'ControlLoopNotification' is abstract + public static class BaseControlLoopNotification extends ControlLoopNotification { + BaseControlLoopNotification(ControlLoopEvent event) { + super(event); + } + } + + /** + * This method is what all of the fuss is about -- we want to create + * a 'ControlLoopNotification' instance compatible with the type of the + * 'event' argument. This is the default implementation -- subclasses of + * 'ControlLoopEvent' may have entries in the table that are specialized + * generate objects that are a subclass of 'ControlLoopNotification' + * appropriate for the transaction type. + * + * @param event this is the event in question + * @return a 'ControlLoopNotification' instance based upon this event + */ + public ControlLoopNotification createNotification(ControlLoopEvent event) { + return new BaseControlLoopNotification(event); + } +} diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Operation.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Operation.java new file mode 100644 index 000000000..5ca62fa82 --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Operation.java @@ -0,0 +1,125 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import java.io.Serializable; + +import org.onap.policy.controlloop.ControlLoopException; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; + +/** + * This is the 'Operation' interface -- each object implementing this + * interface exists for the duration of a single operation. + * + *

An operation typically includes some of the following steps: + * 1) Acquiring locks + * 2) 'Guard' query to see if the operation should proceed (some operations) + * 3) Outgoing request (usually using DMAAP or UEB, but possibly HTTP or HTTPS) + * 4) Incoming response + */ +public interface Operation extends Serializable { + /** + * This method is used as part of sending out the request. In the case of + * DMAAP or UEB interfaces, the method returns the message to be sent, + * but leaves it to the Drools code to do the actual sending. In the case + * of HTTP or HTTPS (e.g. AOTS), the method itself may run the operation in + * a different thread. + * + * @return an object containing the message + * @throws ControlLoopException if it occurs + */ + Object getRequest() throws ControlLoopException; + + /** + * Return the 'Policy' instance associated with this operation. + * + * @return the 'Policy' instance associated with this operation + */ + Policy getPolicy(); + + /** + * The 'state' of an operation is also the state of the 'Transaction' + * instance, while that operation is active. The state is often referenced + * in the 'when' clause of Drools rules, with the rules resembling state + * transition routines (state + event -> operation). In order to avoid + * confusion, the state names should be unique across all operations -- + * this is managed by having each state name begin with 'ACTOR.', where + * 'ACTOR' is the actor associated with the operation. + * + * @return a string value indicating the state of the operation + */ + String getState(); + + /** + * This is set to '1' for the initial attempt, and is incremented by one + * for each retry. Note that a new 'Operation' instance is created for + * each attempt. + * + * @return '1' for the initial attempt of an operation, and incremented + * for each retry + */ + int getAttempt(); + + /** + * Return the result of the operation. + * + * @return the result of the operation + * ('null' if the operation is still in progress) + */ + PolicyResult getResult(); + + /** + * Return the message associated with the completed operation. + * + * @return the message associated with the completed operation + * ('null' if the operation is still in progress) + */ + String getMessage(); + + /** + * An incoming message is being delivered to the operation. The type of + * the message is operation-dependent, and an operation will typically + * understand only one or two message types, and ignore the rest. The + * calling Drools code is written to assume that the transaction has been + * modified -- frequently, a state transition occurs as a result of + * the message. + * + * @param object the incoming message + */ + void incomingMessage(Object object); + + /** + * The operation has timed out. This typically results in the operation + * completing, but that is not enforced. + */ + void timeout(); + + /** + * This method is called on every operation right after its history + * entry has been completed. It gives the operation a chance to do some + * processing based on this entry (e.g. create a 'guard' entry in the DB). + * + * @param histEntry the history entry for this particular operation + */ + default void histEntryCompleted(ControlLoopOperation histEntry) {} +} diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Transaction.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Transaction.java new file mode 100644 index 000000000..65bff684d --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Transaction.java @@ -0,0 +1,717 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import java.io.Serializable; +import java.time.Instant; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import java.util.UUID; + +import lombok.Getter; +import org.drools.core.WorkingMemory; +import org.kie.api.runtime.rule.FactHandle; + +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.ControlLoopNotification; +import org.onap.policy.controlloop.ControlLoopNotificationType; +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.ControlLoopPolicy; +import org.onap.policy.controlloop.policy.FinalResult; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Each instance of this class corresonds to a transaction that is in + * progress. While active, it resides within Drools memory. + */ + +public class Transaction implements Serializable { + + private static Logger logger = LoggerFactory.getLogger(Transaction.class); + + // This table maps 'actor' names to objects implementing the + // 'Actor' interface. 'ServiceLoader' is used to locate and create + // these objects, and populate the table. + private static Map nameToActor = new HashMap<>(); + + static { + // use 'ServiceLoader' to locate all of the 'Actor' implementations + for (Actor actor : + ServiceLoader.load(Actor.class, Actor.class.getClassLoader())) { + logger.debug("Actor: " + actor.getName() + ", " + + actor.getClass()); + nameToActor.put(actor.getName(), actor); + } + } + + private static final long serialVersionUID = 4389211793631707360L; + + // Drools working memory containing this transaction + @Getter + private transient WorkingMemory workingMemory; + + // a service-identifier specified on the associated onset message + @Getter + private String closedLoopControlName; + + // identifies this transaction + @Getter + private UUID requestId; + + // the decoded YAML file for the policy + private ControlLoopPolicy policy; + + // the initial incoming event + private ControlLoopEvent onset = null; + + // operations specific to the type of 'event' + private OnsetAdapter onsetAdapter = null; + + // the current (or most recent) policy in effect + @Getter + private Policy currentPolicy = null; + + // the operation currently in progress + @Getter + private Operation currentOperation = null; + + // a history entry being constructed that is associated with the + // currently running operation + private ControlLoopOperation histEntry = null; + + // a list of completed history entries + @Getter + private List history = new LinkedList<>(); + + // when the transaction completes, this is the final transaction result + @Getter + private FinalResult finalResult = null; + + //message, if any, associated with the result of this operation + private String message = null; + + // this table maps a class name into the associated adjunct + private Map adjuncts = new HashMap<>(); + + /** + * Constructor - initialize a 'Transaction' instance + * (typically invoked from 'Drools'). + * + * @param workingMemory Drools working memory containing this Transaction + * @param closedLoopControlName a string identifying the associated service + * @param requestId uniquely identifies this transaction + * @param policy decoded YAML file containing the policy + */ + public Transaction( + WorkingMemory workingMemory, + String closedLoopControlName, + UUID requestId, + ControlLoopPolicy policy) { + + logger.info("Transaction constructor"); + this.workingMemory = workingMemory; + this.closedLoopControlName = closedLoopControlName; + this.requestId = requestId; + this.policy = policy; + } + + /** + * Return a string indicating the current state of this transaction. + * If there is an operation in progress, the state indicates the operation + * state. Otherwise, the state is 'COMPLETE'. + * + * @return a string indicating the current state of this transaction + */ + public String getState() { + return currentOperation == null + ? "COMPLETE" : currentOperation.getState(); + } + + /** + * Return 'true' if the transaction has completed, and the final result + * indicates failure. + * + * @return 'true' if the transaction has completed, and the final result + * indicates failure + */ + public boolean finalResultFailure() { + return FinalResult.FINAL_SUCCESS != finalResult + && FinalResult.FINAL_OPENLOOP != finalResult + && finalResult != null; + } + + /** + * Return the overall policy timeout value as a String that can be used + * in a Drools timer. + * + * @return the overall policy timeout value as a String that can be used + * in a Drools timer + */ + public String getTimeout() { + return String.valueOf(policy.getControlLoop().getTimeout()) + "s"; + } + + /** + * Return the current operation timeout value as a String that can be used + * in a Drools timer. + * + * @return the current operation timeout value as a String that can be used + * in a Drools timer + */ + public String getOperationTimeout() { + return String.valueOf(currentPolicy.getTimeout()) + "s"; + } + + /** + * Let Drools know the transaction has been modified. + * + *

It is not necessary for Java code to call this method when an incoming + * message is received for an operation, or an operation timeout occurs -- + * the Drools code has been written with the assumption that the transaction + * is modified in these cases. Instead, this method should be called when + * some type of internal occurrence results in a state change, such as when + * an operation acquires a lock after initially being blocked. + */ + public void modify() { + FactHandle handle = workingMemory.getFactHandle(this); + if (handle != null) { + workingMemory.update(handle, this); + } + } + + /** + * Set the initial 'onset' event that started this transaction. + * + * @param event the initial 'onset' event + */ + public void setControlLoopEvent(ControlLoopEvent event) { + if (onset != null) { + logger.error("'Transaction' received unexpected event"); + return; + } + + onset = event; + + // fetch associated 'OnsetAdapter' + onsetAdapter = OnsetAdapter.get(onset); + + // check trigger policy type + if (isOpenLoop(policy.getControlLoop().getTrigger_policy())) { + // no operation is needed for open loops + finalResult = FinalResult.FINAL_OPENLOOP; + modify(); + } else { + // fetch current policy + setPolicyId(policy.getControlLoop().getTrigger_policy()); + } + } + + /** + * Validates the onset by ensuring fields that are required + * for processing are included in the onset. The fields needed + * include the requestId, targetType, and target. + * + * @param onset the initial message that triggers processing + */ + public boolean isControlLoopEventValid(ControlLoopEvent onset) { + if (onset.getRequestId() == null) { + this.message = "No requestID"; + return false; + } else if (onset.getTargetType() == null) { + this.message = "No targetType"; + return false; + } else if (onset.getTarget() == null || onset.getTarget().isEmpty()) { + this.message = "No target field"; + return false; + } + return true; + } + + /** + * Create a 'ControlLoopNotification' from the specified event. Note thet + * the type of the initial 'onset' event is used to determine the type + * of the 'ControlLoopNotification', rather than the event passed to the + * method. + * + * @param event the event used to generate the notification + * (if 'null' is passed, the 'onset' event is used) + * @return the created 'ControlLoopNotification' (or subclass) instance + */ + public ControlLoopNotification getNotification(ControlLoopEvent event) { + ControlLoopNotification notification = + onsetAdapter.createNotification(event == null ? this.onset : event); + + // include entire history + notification.setHistory(new ArrayList(history)); + + return notification; + } + + /** + * This method is called when additional incoming messages are received + * for the transaction. Messages are routed to the current operation, + * any results are processed, and a notification may be returned to + * the caller. + * + * @param object an incoming message, which should be meaningful to the + * operation currently in progress + * @return a notification message if the operation completed, + * or 'null' if it is still in progress + */ + public ControlLoopNotification incomingMessage(Object object) { + ControlLoopNotification notification = null; + if (currentOperation != null) { + currentOperation.incomingMessage(object); + notification = processResult(currentOperation.getResult()); + } else { + logger.error("'Transaction' received unexpected message: " + + object); + } + return notification; + } + + /** + * This method is called from Drools when the current operation times out. + * + * @return a notification message if there is an operation in progress, + * or 'null' if not + */ + public ControlLoopNotification timeout() { + ControlLoopNotification notification = null; + if (currentOperation != null) { + // notify the current operation + currentOperation.timeout(); + + // process the timeout within the transaction + notification = processResult(currentOperation.getResult()); + } else { + logger.error("'Transaction' received unexpected timeout"); + } + return notification; + } + + /** + * This method is called from Drools during a control loop timeout + * to ensure the correct final notification is sent. + */ + public void clTimeout() { + this.finalResult = FinalResult.FINAL_FAILURE_TIMEOUT; + message = "Control Loop timed out"; + currentOperation = null; + } + + /** + * This method is called from Drools to generate a notification message + * when an operation is started. + * + * @return an initial notification message if there is an operation in + * progress, or 'null' if not + */ + public ControlLoopNotification initialOperationNotification() { + if (currentOperation == null || histEntry == null) { + return null; + } + + ControlLoopNotification notification = + onsetAdapter.createNotification(onset); + notification.setNotification(ControlLoopNotificationType.OPERATION); + notification.setMessage(histEntry.toHistory()); + notification.setHistory(new LinkedList()); + for (ControlLoopOperation clo : history) { + if (histEntry.getOperation().equals(clo.getOperation()) + && histEntry.getActor().equals(clo.getActor())) { + notification.getHistory().add(clo); + } + } + return notification; + } + + /** + * Return a final notification message for the entire transaction. + * + * @return a final notification message for the entire transaction, + * or 'null' if we don't have a final result yet + */ + public ControlLoopNotification finalNotification() { + if (finalResult == null) { + return null; + } + + ControlLoopNotification notification = + onsetAdapter.createNotification(onset); + switch (finalResult) { + case FINAL_SUCCESS: + notification.setNotification( + ControlLoopNotificationType.FINAL_SUCCESS); + break; + case FINAL_OPENLOOP: + notification.setNotification( + ControlLoopNotificationType.FINAL_OPENLOOP); + break; + default: + notification.setNotification( + ControlLoopNotificationType.FINAL_FAILURE); + notification.setMessage(this.message); + break; + } + notification.setHistory(history); + return notification; + } + + /** + * Return a 'ControlLoopNotification' instance describing the current operation error. + * + * @return a 'ControlLoopNotification' instance describing the current operation error + */ + public ControlLoopNotification processError() { + ControlLoopNotification notification = null; + if (currentOperation != null) { + // process the error within the transaction + notification = processResult(currentOperation.getResult()); + } + return notification; + } + + /** + * Update the state of the transaction based upon the result of an operation. + * + * @param result if not 'null', this is the result of the current operation + * (if 'null', the operation is still in progress, + * and no changes are made) + * @return if not 'null', this is a notification message that should be + * sent to RUBY + */ + private ControlLoopNotification processResult(PolicyResult result) { + if (result == null) { + modify(); + return null; + } + String nextPolicy = null; + + ControlLoopOperation saveHistEntry = histEntry; + completeHistEntry(result); + + final ControlLoopNotification notification = processResult_HistEntry(saveHistEntry, result); + + // If there is a message from the operation then we set it to be + // used by the control loop notifications + message = currentOperation.getMessage(); + + // set the value 'nextPolicy' based upon the result of the operation + switch (result) { + case SUCCESS: + nextPolicy = currentPolicy.getSuccess(); + break; + + case FAILURE: + nextPolicy = processResult_Failure(); + break; + + case FAILURE_TIMEOUT: + nextPolicy = currentPolicy.getFailure_timeout(); + message = "Operation timed out"; + break; + + case FAILURE_RETRIES: + nextPolicy = currentPolicy.getFailure_retries(); + message = "Control Loop reached failure retry limit"; + break; + + case FAILURE_EXCEPTION: + nextPolicy = currentPolicy.getFailure_exception(); + break; + + case FAILURE_GUARD: + nextPolicy = currentPolicy.getFailure_guard(); + break; + + default: + break; + } + + if (nextPolicy != null) { + finalResult = FinalResult.toResult(nextPolicy); + if (finalResult == null) { + // it must be the next state + logger.debug("advancing to next operation"); + setPolicyId(nextPolicy); + } else { + logger.debug("moving to COMPLETE state"); + currentOperation = null; + } + } else { + logger.debug("doing retry with current actor"); + } + + modify(); + return notification; + } + + // returns a notification message based on the history entry + private ControlLoopNotification processResult_HistEntry(ControlLoopOperation hist, PolicyResult result) { + if (hist == null) { + return null; + } + + // generate notification, containing operation history + ControlLoopNotification notification = onsetAdapter.createNotification(onset); + notification.setNotification( + result == PolicyResult.SUCCESS + ? ControlLoopNotificationType.OPERATION_SUCCESS + : ControlLoopNotificationType.OPERATION_FAILURE); + notification.setMessage(hist.toHistory()); + + // include the subset of history that pertains to this + // actor and operation + notification.setHistory(new LinkedList()); + for (ControlLoopOperation clo : history) { + if (hist.getOperation().equals(clo.getOperation()) + && hist.getActor().equals(clo.getActor())) { + notification.getHistory().add(clo); + } + } + + return notification; + } + + // returns the next policy if the current operation fails + private String processResult_Failure() { + String nextPolicy = null; + int attempt = currentOperation.getAttempt(); + if (attempt <= currentPolicy.getRetry()) { + // operation failed, but there are retries left + Actor actor = nameToActor.get(currentPolicy.getActor()); + if (actor != null) { + attempt += 1; + logger.debug("found Actor, attempt " + attempt); + currentOperation = + actor.createOperation(this, currentPolicy, onset, attempt); + createHistEntry(); + } else { + logger.error("'Transaction' can't find actor " + + currentPolicy.getActor()); + } + } else { + // operation failed, and no retries (or no retries left) + nextPolicy = (attempt == 1 + ? currentPolicy.getFailure() + : currentPolicy.getFailure_retries()); + logger.debug("moving to policy " + nextPolicy); + } + return nextPolicy; + } + + /** + * Create a history entry at the beginning of an operation, and store it + * in the 'histEntry' instance variable. + */ + private void createHistEntry() { + histEntry = new ControlLoopOperation(); + histEntry.setActor(currentPolicy.getActor()); + histEntry.setOperation(currentPolicy.getRecipe()); + histEntry.setTarget(currentPolicy.getTarget().toString()); + histEntry.setSubRequestId(String.valueOf(currentOperation.getAttempt())); + + // histEntry.end - we will set this one later + // histEntry.outcome - we will set this one later + // histEntry.message - we will set this one later + } + + /** + * Finish up the history entry at the end of an operation, and add it + * to the history list. + * + * @param result this is the result of the operation, which can't be 'null' + */ + private void completeHistEntry(PolicyResult result) { + if (histEntry == null) { + return; + } + + // append current entry to history + histEntry.setEnd(Instant.now()); + histEntry.setOutcome(result.toString()); + histEntry.setMessage(currentOperation.getMessage()); + history.add(histEntry); + + // give current operation a chance to act on it + currentOperation.histEntryCompleted(histEntry); + logger.debug("histEntry = {}", histEntry); + histEntry = null; + } + + /** + * Look up the identifier for the next policy, and prepare to start that + * operation. + * + * @param id this is the identifier associated with the policy + */ + private void setPolicyId(String id) { + currentPolicy = null; + currentOperation = null; + + // search through the policies for a matching 'id' + for (Policy tmp : policy.getPolicies()) { + if (id.equals(tmp.getId())) { + // found a match + currentPolicy = tmp; + break; + } + } + + if (currentPolicy != null) { + // locate the 'Actor' associated with 'currentPolicy' + Actor actor = nameToActor.get(currentPolicy.getActor()); + if (actor != null) { + // found the associated 'Actor' instance + currentOperation = + actor.createOperation(this, currentPolicy, onset, 1); + createHistEntry(); + } else { + logger.error("'Transaction' can't find actor " + + currentPolicy.getActor()); + } + } else { + logger.error("Transaction' can't find policy " + id); + } + + if (currentOperation == null) { + + // either we couldn't find the actor or the operation -- + // the transaction fails + finalResult = FinalResult.FINAL_FAILURE; + } + } + + private boolean isOpenLoop(String policyId) { + return FinalResult.FINAL_OPENLOOP.name().equalsIgnoreCase(policyId); + } + + /** + * This method sets the message for a control loop notification + * in the case where a custom message wants to be sent due to + * error processing, etc. + * + * @param message the message to be set for the control loop notification + */ + public void setNotificationMessage(String message) { + this.message = message; + } + + /** + * Return the notification message of this transaction. + * + * @return the notification message of this transaction + */ + public String getNotificationMessage() { + return this.message; + } + + /* ============================================================ */ + + /** + * Subclasses of 'Adjunct' provide data and methods to support one or + * more Actors/Operations, but are stored within the 'Transaction' + * instance. + */ + public static interface Adjunct extends Serializable { + /** + * Called when an adjunct is automatically created as a result of + * a 'getAdjunct' call. + * + * @param transaction the transaction containing the adjunct + */ + public default void init(Transaction transaction) {} + + /** + * Called for each adjunct when the transaction completes, and is + * removed from Drools memory. Any adjunct-specific cleanup can be + * done at this point (e.g. freeing locks). + */ + public default void cleanup(Transaction transaction) {} + } + + /** + * This is a method of class 'Transaction', and returns an adjunct of + * the specified class (it is created if it doesn't exist). + * + * @param clazz this is the class of the adjunct + * @return an adjunct of the specified class ('null' may be returned if + * the 'newInstance' method is unable to create the adjunct) + */ + public T getAdjunct(final Class clazz) { + return clazz.cast(adjuncts.computeIfAbsent(clazz, cl -> { + T adjunct = null; + try { + // create the adjunct (may trigger an exception) + adjunct = clazz.newInstance(); + + // initialize the adjunct (may also trigger an exception */ + adjunct.init(Transaction.this); + } catch (Exception e) { + logger.error("Transaction can't create adjunct of {}", cl, e); + } + return adjunct; + })); + } + + /** + * Explicitly create an adjunct -- this is useful when the adjunct + * initialization requires that some parameters be passed. + * + * @param adjunct this is the adjunct to insert into the table + * @return 'true' if successful + * ('false' is returned if an adjunct with this class already exists) + */ + public boolean putAdjunct(Adjunct adjunct) { + return adjuncts.putIfAbsent(adjunct.getClass(), adjunct) == null; + } + + /** + * This method needs to be called when the transaction completes, which + * is typically right after it is removed from Drools memory. + */ + public void cleanup() { + // create a list containing all of the adjuncts (in no particular order) + List values; + synchronized (adjuncts) { + values = new LinkedList<>(adjuncts.values()); + } + + // iterate over the list + for (Adjunct a : values) { + try { + // call the 'cleanup' method on the adjunct + a.cleanup(this); + } catch (Exception e) { + logger.error("Transaction.cleanup exception", e); + } + } + } +} diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Util.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Util.java new file mode 100644 index 000000000..8e6d98bf7 --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Util.java @@ -0,0 +1,62 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import java.util.List; + +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.common.endpoints.event.comm.TopicSink; +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.core.PolicySession; +import org.onap.policy.drools.system.PolicyController; +import org.onap.policy.drools.system.PolicyControllerConstants; +import org.onap.policy.drools.system.PolicyEngineConstants; + +/** + * This class contains static utility methods. + */ +public class Util { + /** + * Find the PolicyController associated with the specified PolicySession. + * + * @param session the current PolicySession + * @return the associated PolicyController (or 'null' if not found) + */ + public static PolicyController getPolicyController(PolicySession session) { + PolicyContainer container = session.getPolicyContainer(); + return PolicyControllerConstants.getFactory().get( + container.getGroupId(), container.getArtifactId()); + } + + /** + * Send a UEB/DMAAP message to the specified topic, using the specified + * PolicyController. + * + * @param topic UEB/DMAAP topic + * @param event the message to encode, and send + * @return 'true' if successful, 'false' if delivery failed + * @throws IllegalStateException if the topic can't be found, + * or there are multiple topics with the same name + */ + public static boolean deliver(String topic, Object event) { + return PolicyEngineConstants.getManager().deliver(topic, event); + } +} diff --git a/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/ActorOperationTest.java b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/ActorOperationTest.java new file mode 100644 index 000000000..4cbd05e03 --- /dev/null +++ b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/ActorOperationTest.java @@ -0,0 +1,116 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import org.junit.Test; + +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.ControlLoopException; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.controlloop.policy.PolicyResult; + +public class ActorOperationTest { + + public static final String ACTOR_NAME = "test"; + public static final String STATE = "COMPLETE"; + + public static class TestOperation implements Operation { + + @Override + public Object getRequest() throws ControlLoopException { + return "request"; + } + + @Override + public Policy getPolicy() { + return null; + } + + @Override + public String getState() { + return STATE; + } + + @Override + public int getAttempt() { + return 0; + } + + @Override + public PolicyResult getResult() { + return PolicyResult.SUCCESS; + } + + @Override + public String getMessage() { + return "success"; + } + + @Override + public void incomingMessage(Object object) { + return; + } + + @Override + public void timeout() { + return; + } + + } + + public static class TestActor implements Actor { + + @Override + public String getName() { + return ACTOR_NAME; + } + + @Override + public Operation createOperation(Transaction transaction, Policy policy, ControlLoopEvent onset, int attempt) { + return new TestOperation(); + } + + } + + @Test + public void getNameTest() { + Actor actor = new TestActor(); + assertEquals(ACTOR_NAME, actor.getName()); + } + + @Test + public void operationTest() throws ControlLoopException { + Actor actor = new TestActor(); + Operation operation = actor.createOperation(null, null, null, 0); + assertNotNull(operation); + assertEquals("request", operation.getRequest()); + assertNull(operation.getPolicy()); + assertEquals(STATE, operation.getState()); + assertEquals(0, operation.getAttempt()); + assertEquals(PolicyResult.SUCCESS, operation.getResult()); + assertEquals("success", operation.getMessage()); + } + +} diff --git a/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/GuardAdjunctTest.java b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/GuardAdjunctTest.java new file mode 100644 index 000000000..eacfc8f6f --- /dev/null +++ b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/GuardAdjunctTest.java @@ -0,0 +1,103 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +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.UUID; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.guard.GuardContext; +import org.powermock.reflect.Whitebox; + +public class GuardAdjunctTest { + private static final String ADJUNCT_CONTEXT_FIELD = "context"; + private static final String ADJUNCT_TRANSACTION_FIELD = "transaction"; + + private static GuardAdjunct adjunct; + private static Transaction transaction; + private static GuardContext context; + + /** + * Class-level initialization. + */ + @BeforeClass + public static void setup() { + transaction = new Transaction(null, "testCL", UUID.randomUUID(), null); + context = mock(GuardContext.class); + + GuardAdjunct.create(transaction, context); + adjunct = transaction.getAdjunct(GuardAdjunct.class); + } + + @Test + public void createGuardAdjunctTest() { + assertEquals(context, adjunct.get()); + } + + @Test + public void asyncQueryTest() { + Policy policy = new Policy(); + policy.setActor("APPCLCM"); + policy.setRecipe("test"); + + assertTrue(adjunct.asyncQuery(policy, "testTarget", UUID.randomUUID().toString())); + + GuardContext savedContext = Whitebox.getInternalState(adjunct, ADJUNCT_CONTEXT_FIELD); + Whitebox.setInternalState(adjunct, ADJUNCT_CONTEXT_FIELD, (GuardContext)null); + + try { + assertFalse(adjunct.asyncQuery(policy, "testTarget", UUID.randomUUID().toString())); + } finally { + Whitebox.setInternalState(adjunct, ADJUNCT_CONTEXT_FIELD, savedContext); + } + } + + @Test + public void asyncCreateDbEntryTest() { + ControlLoopOperation op = new ControlLoopOperation(); + op.setStart(Instant.now().minusSeconds(1)); + op.setEnd(Instant.now()); + op.setActor("testActor"); + op.setOperation("testOperation"); + op.setSubRequestId("0"); + op.setMessage("test"); + op.setOutcome("success"); + + adjunct.asyncCreateDbEntry(op, "testTarget"); + verify(context, times(1)).asyncCreateDbEntry(op.getStart(), op.getEnd(), + transaction.getClosedLoopControlName(), op.getActor(), + op.getOperation(), "testTarget", transaction.getRequestId().toString(), + op.getSubRequestId(), op.getMessage(), op.getOutcome()); + + } +} diff --git a/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/TransactionTest.java b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/TransactionTest.java new file mode 100644 index 000000000..3c186ce6d --- /dev/null +++ b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/TransactionTest.java @@ -0,0 +1,230 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.time.Instant; +import java.util.LinkedList; +import java.util.UUID; + +import org.drools.core.WorkingMemory; +import org.drools.core.impl.StatefulKnowledgeSessionImpl; +import org.junit.Ignore; +import org.junit.Test; + +import org.onap.policy.controlloop.ControlLoopEventStatus; +import org.onap.policy.controlloop.ControlLoopNotification; +import org.onap.policy.controlloop.ControlLoopTargetType; +import org.onap.policy.controlloop.VirtualControlLoopEvent; +import org.onap.policy.controlloop.policy.ControlLoop; +import org.onap.policy.controlloop.policy.ControlLoopPolicy; +import org.onap.policy.controlloop.policy.FinalResult; +import org.onap.policy.controlloop.policy.Policy; + +public class TransactionTest { + + private VirtualControlLoopEvent setControlLoopEvent(UUID requestId, String closedLoopControlName, + Instant eventStart, String targetType, String target) { + + VirtualControlLoopEvent event = new VirtualControlLoopEvent(); + event.setClosedLoopControlName(closedLoopControlName); + event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET); + event.setRequestId(requestId); + event.setClosedLoopAlarmStart(eventStart); + event.setTarget(target); + event.setTargetType(targetType); + event.getAai().put("vserver.is-closed-loop-disabled", "false"); + event.getAai().put("complex.state", "NJ"); + event.getAai().put("vserver.l-interface.interface-name", "89ee9ee6-1e96-4063-b690-aa5ca9f73b32"); + event.getAai().put("vserver.l-interface.l3-interface-ipv4-address-list.l3-inteface-ipv4-address", + "135.144.3.49"); + event.getAai().put("vserver.l-interface.l3-interface-ipv6-address-list.l3-inteface-ipv6-address", null); + event.getAai().put("vserver.in-maint", "N"); + event.getAai().put("complex.city", "AAIDefault"); + event.getAai().put("vserver.vserver-id", "aa7a24f9-8791-491f-b31a-c8ba5ad9e2aa"); + event.getAai().put("vserver.l-interface.network-name", "vUSP_DPA3_OAM_3750"); + event.getAai().put("vserver.vserver-name", "ctsf0002vm013"); + event.getAai().put("generic-vnf.vnf-name", "ctsf0002v"); + event.getAai().put("generic-vnf.service-id", "e433710f-9217-458d-a79d-1c7aff376d89"); + event.getAai().put("vserver.selflink", "https://compute-aic.dpa3.cci.att.com:8774/v2/d0719b845a804b368f8ac0bba39e188b/servers/aa7a24f9-8791-491f-b31a-c8ba5ad9e2aa"); + event.getAai().put("generic-vnf.vnf-type", "vUSP - vCTS"); + event.getAai().put("tenant.tenant-id", "d0719b845a804b368f8ac0bba39e188b"); + event.getAai().put("cloud-region.identity-url", ""); + event.getAai().put("vserver.prov-status", "PROV"); + event.getAai().put("complex.physical-location-id", "LSLEILAA"); + + return event; + } + + private ControlLoopPolicy createControlLoop() { + + ControlLoop controlLoop = new ControlLoop(); + controlLoop.setControlLoopName("cltest"); + controlLoop.setTrigger_policy("testid"); + controlLoop.setTimeout(15); + + Policy policy = new Policy(); + policy.setActor("APPCLCM"); + policy.setId("testid"); + policy.setName("policytest"); + policy.setRecipe("restart"); + policy.setRetry(1); + policy.setTimeout(10); + policy.setSuccess(FinalResult.FINAL_SUCCESS.toString()); + policy.setFailure(FinalResult.FINAL_FAILURE.toString()); + policy.setFailure_exception(FinalResult.FINAL_FAILURE_EXCEPTION.toString()); + policy.setFailure_guard(FinalResult.FINAL_FAILURE_GUARD.toString()); + policy.setFailure_retries(FinalResult.FINAL_FAILURE_RETRIES.toString()); + policy.setFailure_timeout(FinalResult.FINAL_FAILURE_TIMEOUT.toString()); + + LinkedList policies = new LinkedList<>(); + policies.add(policy); + + ControlLoopPolicy controlLoopPolicy = new ControlLoopPolicy(); + controlLoopPolicy.setControlLoop(controlLoop); + controlLoopPolicy.setPolicies(policies); + + return controlLoopPolicy; + } + + @Test + public void validControlLoopEventTest() { + VirtualControlLoopEvent event = setControlLoopEvent(UUID.randomUUID(), + "cltest", Instant.now(), ControlLoopTargetType.VM, "vserver.vserver-name"); + Transaction transaction = new Transaction(null, "clvusptest", event.getRequestId(), createControlLoop()); + assertTrue(transaction.isControlLoopEventValid(event)); + } + + @Test + public void noRequestIdControlLoopEventTest() { + VirtualControlLoopEvent event = setControlLoopEvent(null, + "cltest", Instant.now(), ControlLoopTargetType.VM, "vserver.vserver-name"); + Transaction transaction = new Transaction(null, "clvusptest", event.getRequestId(), createControlLoop()); + assertFalse(transaction.isControlLoopEventValid(event)); + assertEquals("No requestID", transaction.getNotificationMessage()); + } + + @Test + public void noTargetTypeControlLoopEventTest() { + VirtualControlLoopEvent event = setControlLoopEvent(UUID.randomUUID(), + "cltest", Instant.now(), null, "vserver.vserver-name"); + Transaction transaction = new Transaction(null, "clvusptest", event.getRequestId(), createControlLoop()); + assertFalse(transaction.isControlLoopEventValid(event)); + assertEquals("No targetType", transaction.getNotificationMessage()); + } + + @Test + public void noTargetControlLoopEventTest() { + VirtualControlLoopEvent event = setControlLoopEvent(UUID.randomUUID(), + "cltest", Instant.now(), ControlLoopTargetType.VM, null); + Transaction transaction = new Transaction(null, "clvusptest", event.getRequestId(), createControlLoop()); + assertFalse(transaction.isControlLoopEventValid(event)); + assertEquals("No target field", transaction.getNotificationMessage()); + } + + @Test + public void getClosedLoopControlNameTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + assertEquals("clvusptest", transaction.getClosedLoopControlName()); + } + + @Test + public void getRequestIdTest() { + UUID requestId = UUID.randomUUID(); + Transaction transaction = new Transaction(null, "clvusptest", requestId, createControlLoop()); + assertEquals(requestId, transaction.getRequestId()); + } + + @Test + public void getWorkingMemoryTest() { + // Create mock working session + StatefulKnowledgeSessionImpl mockWorkingMemory = mock(StatefulKnowledgeSessionImpl.class); + Transaction transaction = new Transaction(mockWorkingMemory, "clvusptest", + UUID.randomUUID(), createControlLoop()); + assertEquals(mockWorkingMemory, transaction.getWorkingMemory()); + } + + @Test + public void getStateCompleteTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + assertEquals("COMPLETE", transaction.getState()); + } + + @Test + public void getFinalResultTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + assertEquals(null, transaction.getFinalResult()); + } + + @Test + public void finalResultFailureTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + assertFalse(transaction.finalResultFailure()); + } + + @Test + public void getTimeoutTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + assertEquals("15s", transaction.getTimeout()); + } + + @Test + public void getOperationTimeoutTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + VirtualControlLoopEvent onset = setControlLoopEvent(UUID.randomUUID(), + "cltest", null, ControlLoopTargetType.VM, "vserver.vserver-name"); + transaction.setControlLoopEvent(onset); + assertEquals("10s", transaction.getOperationTimeout()); + } + + @Test + public void getCurrentPolicy() { + ControlLoopPolicy controlLoopPolicy = createControlLoop(); + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), controlLoopPolicy); + VirtualControlLoopEvent onset = setControlLoopEvent(UUID.randomUUID(), + "cltest", null, ControlLoopTargetType.VM, "vserver.vserver-name"); + transaction.setControlLoopEvent(onset); + assertEquals(controlLoopPolicy.getPolicies().get(0), transaction.getCurrentPolicy()); + } + + @Test + public void getOperationTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + assertEquals(null, transaction.getCurrentOperation()); + } + + @Test + public void getNotificationTest() { + Transaction transaction = new Transaction(null, "clvusptest", UUID.randomUUID(), createControlLoop()); + VirtualControlLoopEvent onset = setControlLoopEvent(UUID.randomUUID(), + "cltest", null, ControlLoopTargetType.VM, "vserver.vserver-name"); + transaction.setControlLoopEvent(onset); + ControlLoopNotification notification = transaction.getNotification(onset); + assertEquals(onset.getClosedLoopControlName(), notification.getClosedLoopControlName()); + assertEquals(onset.getRequestId(), notification.getRequestId()); + assertEquals(onset.getTarget(), notification.getTarget()); + } +} diff --git a/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/UtilTest.java b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/UtilTest.java new file mode 100644 index 000000000..53d97209b --- /dev/null +++ b/controlloop/m2/base/src/test/java/org/onap/policy/m2/base/UtilTest.java @@ -0,0 +1,73 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * 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.m2.base; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Properties; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; +import org.onap.policy.drools.core.PolicyContainer; +import org.onap.policy.drools.core.PolicySession; +import org.onap.policy.drools.system.PolicyController; + +public class UtilTest { + + private static PolicySession session; + + /** + * Class-level initialization. + */ + @BeforeClass + public static void setup() { + PolicyContainer container = mock(PolicyContainer.class); + when(container.getGroupId()).thenReturn("org.onap.policy"); + when(container.getArtifactId()).thenReturn("test"); + when(container.getVersion()).thenReturn("1.0.0"); + + session = mock(PolicySession.class); + when(session.getPolicyContainer()).thenReturn(container); + } + + @Test + public void deliverTest() { + Properties prop = new Properties(); + prop.put("noop.sink.topics", "testTopic"); + TopicEndpointManager.getManager().addTopicSinks(prop); + + // throws an exception: + // java.lang.IllegalStateException: Policy Engine is stopped + // if policy engine is started, it still throws an exception: + // java.lang.IllegalArgumentException: no reverse coder has been found + //assertTrue(Util.deliver("testTopic", "test")); + } + + @Test(expected = IllegalStateException.class) + public void deliverNoTopicTest() { + Util.deliver("noTopic", "test"); + } +} -- cgit 1.2.3-korg