diff options
author | Straubs, Ralph (rs8887) <rs8887@att.com> | 2019-11-19 04:11:23 -0600 |
---|---|---|
committer | Straubs, Ralph (rs8887) <rs8887@att.com> | 2020-01-10 03:20:23 -0600 |
commit | 3e05cb41202145e113853392e9837abf3f6ec12c (patch) | |
tree | 0c504018436c3933f563caa37c3ea0512c82181e /controlloop/m2/base/src/main | |
parent | 927c7c177670a812a4a4139281ef84e85b520645 (diff) |
Add m2 model, including the LCM application
Issue-ID: POLICY-1948
Change-Id: I18a5231d3102073c928a591c9e91b241b7093680
Signed-off-by: Straubs, Ralph (rs8887) <rs8887@att.com>
Diffstat (limited to 'controlloop/m2/base/src/main')
6 files changed, 1235 insertions, 0 deletions
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. + * + * <p>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<Class,OnsetAdapter> 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<Class> 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<Class>(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. + * + * <p>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<String,Actor> 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<ControlLoopOperation> 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<Class, Adjunct> 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. + * + * <p>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<ControlLoopOperation>(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<ControlLoopOperation>()); + 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<ControlLoopOperation>()); + 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 extends Adjunct> T getAdjunct(final Class<T> 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<Adjunct> 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); + } +} |