summaryrefslogtreecommitdiffstats
path: root/controlloop/m2/base/src/main
diff options
context:
space:
mode:
authorStraubs, Ralph (rs8887) <rs8887@att.com>2019-11-19 04:11:23 -0600
committerStraubs, Ralph (rs8887) <rs8887@att.com>2020-01-10 03:20:23 -0600
commit3e05cb41202145e113853392e9837abf3f6ec12c (patch)
tree0c504018436c3933f563caa37c3ea0512c82181e /controlloop/m2/base/src/main
parent927c7c177670a812a4a4139281ef84e85b520645 (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')
-rw-r--r--controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java52
-rw-r--r--controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java123
-rw-r--r--controlloop/m2/base/src/main/java/org/onap/policy/m2/base/OnsetAdapter.java156
-rw-r--r--controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Operation.java125
-rw-r--r--controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Transaction.java717
-rw-r--r--controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Util.java62
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);
+ }
+}