aboutsummaryrefslogtreecommitdiffstats
path: root/controlloop
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
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')
-rw-r--r--controlloop/m2/adapters/pom.xml46
-rw-r--r--controlloop/m2/adapters/src/main/java/org/onap/policy/m2/adapters/VirtualOnsetAdapter.java62
-rw-r--r--controlloop/m2/adapters/src/test/java/org/onap/policy/m2/adapters/VirtualOnsetAdapterTest.java53
-rw-r--r--controlloop/m2/appclcm/pom.xml86
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java76
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java248
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java703
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java58
-rw-r--r--controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor1
-rw-r--r--controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmHealthCheckOperationTest.java281
-rw-r--r--controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmOperationTest.java708
-rw-r--r--controlloop/m2/appclcm/src/test/java/model/AppcLcmResponseCodeTest.java44
-rw-r--r--controlloop/m2/base/pom.xml105
-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
-rw-r--r--controlloop/m2/base/src/test/java/org/onap/policy/m2/base/ActorOperationTest.java116
-rw-r--r--controlloop/m2/base/src/test/java/org/onap/policy/m2/base/GuardAdjunctTest.java103
-rw-r--r--controlloop/m2/base/src/test/java/org/onap/policy/m2/base/TransactionTest.java230
-rw-r--r--controlloop/m2/base/src/test/java/org/onap/policy/m2/base/UtilTest.java73
-rw-r--r--controlloop/m2/feature-controlloop-m2/pom.xml126
-rw-r--r--controlloop/m2/feature-controlloop-m2/src/assembly/assemble_zip.xml80
-rw-r--r--controlloop/m2/guard/pom.xml84
-rw-r--r--controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java400
-rw-r--r--controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java139
-rw-r--r--controlloop/m2/lock/pom.xml55
-rw-r--r--controlloop/m2/lock/src/main/java/org/onap/policy/drools/m2/lock/LockAdjunct.java164
-rw-r--r--controlloop/m2/lock/src/test/java/org/onap/policy/drools/m2/lock/LockAdjunctTest.java97
-rw-r--r--controlloop/m2/pom.xml49
-rw-r--r--controlloop/m2/test/pom.xml100
-rw-r--r--controlloop/m2/test/src/test/java/org/onap/policy/m2/test/AppcLcmTest.java318
-rw-r--r--controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimDmaap.java266
-rw-r--r--controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimGuard.java65
-rw-r--r--controlloop/m2/test/src/test/java/org/onap/policy/m2/test/Util.java502
-rw-r--r--controlloop/m2/test/src/test/resources/appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml83
-rw-r--r--controlloop/m2/test/src/test/resources/appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl649
-rw-r--r--controlloop/m2/test/src/test/resources/appclcm/controller.properties38
-rw-r--r--controlloop/m2/test/src/test/resources/appclcm/kmodule.xml26
-rw-r--r--controlloop/m2/test/src/test/resources/appclcm/pom.xml29
-rw-r--r--controlloop/m2/util/pom.xml47
-rw-r--r--controlloop/m2/util/src/main/java/org/onap/policy/util/DroolsSessionCommonSerializable.java136
-rw-r--r--controlloop/m2/util/src/test/java/org/onap/policy/util/DroolsSessionCommonSerializableTest.java36
-rw-r--r--controlloop/pom.xml1
46 files changed, 7718 insertions, 0 deletions
diff --git a/controlloop/m2/adapters/pom.xml b/controlloop/m2/adapters/pom.xml
new file mode 100644
index 000000000..b4cd2907a
--- /dev/null
+++ b/controlloop/m2/adapters/pom.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>adapters</artifactId>
+ <name>adapters</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>base</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/controlloop/m2/adapters/src/main/java/org/onap/policy/m2/adapters/VirtualOnsetAdapter.java b/controlloop/m2/adapters/src/main/java/org/onap/policy/m2/adapters/VirtualOnsetAdapter.java
new file mode 100644
index 000000000..e9ca1164d
--- /dev/null
+++ b/controlloop/m2/adapters/src/main/java/org/onap/policy/m2/adapters/VirtualOnsetAdapter.java
@@ -0,0 +1,62 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/adapters
+ * ================================================================================
+ * 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.adapters;
+
+import java.io.Serializable;
+
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopNotification;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.VirtualControlLoopNotification;
+
+import org.onap.policy.m2.base.OnsetAdapter;
+
+public class VirtualOnsetAdapter extends OnsetAdapter implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private static VirtualOnsetAdapter instance = new VirtualOnsetAdapter();
+
+ /**
+ * This method is called to register the 'VirtualOnsetAdapter' instance
+ * under the 'VirtualControlLoopEvent' class. This method called in the
+ * static initialization code of the 'Actor' classes that use this
+ * adapter -- namely, 'AppcLcmActor'.
+ */
+ public static void register() {
+ OnsetAdapter.register(VirtualControlLoopEvent.class, instance);
+ }
+
+ /**
+ * This method overrides the associated 'OnsetAdapter' method.
+ */
+ @Override
+ public ControlLoopNotification createNotification(ControlLoopEvent event) {
+ if (event instanceof VirtualControlLoopEvent) {
+ return new VirtualControlLoopNotification((VirtualControlLoopEvent)event);
+ }
+
+ // Right now, the onset event from the transaction is used to locate
+ // the adapter. It is expected that the 'event' passed here will
+ // be of the same class, but that isn't always guaranteed. If this
+ // is not the case, the appropriate adapter is located in this way.
+ return OnsetAdapter.get(event).createNotification(event);
+ }
+}
diff --git a/controlloop/m2/adapters/src/test/java/org/onap/policy/m2/adapters/VirtualOnsetAdapterTest.java b/controlloop/m2/adapters/src/test/java/org/onap/policy/m2/adapters/VirtualOnsetAdapterTest.java
new file mode 100644
index 000000000..0ecbda672
--- /dev/null
+++ b/controlloop/m2/adapters/src/test/java/org/onap/policy/m2/adapters/VirtualOnsetAdapterTest.java
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * adapters
+ * ================================================================================
+ * 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.adapters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopNotification;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.VirtualControlLoopNotification;
+import org.onap.policy.m2.base.OnsetAdapter;
+
+public class VirtualOnsetAdapterTest {
+
+ @Test
+ public void test() {
+ VirtualOnsetAdapter.register();
+ VirtualControlLoopEvent virtualControlLoopEvent = new VirtualControlLoopEvent();
+ VirtualOnsetAdapter virtualOnsetAdapter =
+ VirtualOnsetAdapter.class.cast(OnsetAdapter.get(virtualControlLoopEvent));
+ assertTrue(virtualOnsetAdapter != null);
+
+ ControlLoopNotification notification = virtualOnsetAdapter.createNotification(virtualControlLoopEvent);
+ assertTrue(notification != null);
+ // we want an exact class match, so 'instanceOf' is not being used
+ assertEquals(VirtualControlLoopNotification.class, notification.getClass());
+
+ ControlLoopEvent controlLoopEvent = new ControlLoopEvent() {};
+ notification = virtualOnsetAdapter.createNotification(controlLoopEvent);
+ assertTrue(notification != null);
+ }
+}
diff --git a/controlloop/m2/appclcm/pom.xml b/controlloop/m2/appclcm/pom.xml
new file mode 100644
index 000000000..78200454a
--- /dev/null
+++ b/controlloop/m2/appclcm/pom.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>appclcm</artifactId>
+ <name>Experimental Control Loop Model - appclcm</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>adapters</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>base</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>lock</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>appclcm</artifactId>
+ <version>${policy.models.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java
new file mode 100644
index 000000000..f89d3b873
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java
@@ -0,0 +1,76 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * 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.appclcm;
+
+import java.io.Serializable;
+
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+
+import org.onap.policy.m2.adapters.VirtualOnsetAdapter;
+import org.onap.policy.m2.base.Actor;
+import org.onap.policy.m2.base.Operation;
+import org.onap.policy.m2.base.Transaction;
+
+/**
+ * A single instance of this class is created, and resides within the
+ * 'nameToActor' table within class 'Transaction', under the key 'APPC'.
+ */
+public class AppcLcmActor implements Actor, Serializable {
+ /* *******************/
+ /* 'Actor' interface */
+ /* *******************/
+
+ private static final long serialVersionUID = -593438898257647144L;
+
+
+ static {
+ // ensures that 'VirtualOnsetAdapter' has an entry in the
+ // 'OnsetAdapter' table
+ VirtualOnsetAdapter.register();
+ }
+
+ /**
+ * Return the name associated with this 'Actor'.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
+ return "APPCLCM";
+ }
+
+ /**
+ * Create an 'Operation' for this 'Actor'.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public Operation createOperation(
+ Transaction transaction, Policy policy, ControlLoopEvent onset,
+ int attempt) {
+
+ if ("healthcheck".equalsIgnoreCase(policy.getRecipe())) {
+ return new AppcLcmHealthCheckOperation(transaction, policy, onset, attempt);
+ }
+ return new AppcLcmOperation(transaction, policy, onset, attempt);
+ }
+}
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java
new file mode 100644
index 000000000..42e06f98f
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java
@@ -0,0 +1,248 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * 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.appclcm;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmOutput;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.guard.PolicyGuardResponse;
+import org.onap.policy.m2.appclcm.model.AppcLcmResponseCode;
+import org.onap.policy.m2.base.Transaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppcLcmHealthCheckOperation extends AppcLcmOperation {
+ public static final String DCAE_IPV4_ADDR =
+ "vserver.l-interface.l3-interface-ipv4-address-list.l3-inteface-ipv4-address";
+
+ private static Logger logger = LoggerFactory.getLogger(AppcLcmHealthCheckOperation.class);
+
+ private static final long serialVersionUID = 4969322301462776173L;
+
+ public AppcLcmHealthCheckOperation(Transaction transaction, Policy policy,
+ ControlLoopEvent onset, int attempt) {
+ super(transaction, policy, onset, attempt);
+ }
+
+ /**
+ * This method will attempt to deserialize the json payload from appc and
+ * then parse the response to determine if the vnf is healthy or unhealthy.
+ * The "state" field in the payload will contain "healthy" or "unhealthy"
+ * based on the condition of the vnf.
+ *
+ * @param jsonPayload
+ * the appc lcm response json payload
+ * @return the string that contains the state of the vnf
+ */
+ private String getVnfHealthState(String jsonPayload) {
+ HashMap<String, Object> healthCheckPayloadMap;
+ try {
+ healthCheckPayloadMap = coder.decode(jsonPayload, HashMap.class);
+ } catch (CoderException e) {
+ return null;
+ }
+
+ String stateOfHealth = null;
+ if (healthCheckPayloadMap.containsKey("state")) {
+ stateOfHealth = healthCheckPayloadMap.get("state").toString();
+ } else {
+ return null;
+ }
+ return stateOfHealth;
+ }
+
+ /**
+ * An incoming message is being delivered to the operation.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void incomingMessage(Object object) {
+ if (!(object instanceof AppcLcmDmaapWrapper)) {
+ if (object instanceof PolicyGuardResponse) {
+ incomingGuardMessage((PolicyGuardResponse) object);
+ return;
+ }
+ // ignore this message (not sure why we even got it)
+ return;
+ }
+
+ // If we reach this point, we have a 'AppcLcmDmaapWrapper' instance.
+ // The rest of this method is mostly copied from
+ // 'ControlLoopOperationManager.onResponse'.
+
+ AppcLcmOutput response = ((AppcLcmDmaapWrapper)object).getBody().getOutput();
+
+ //
+ // Determine which subrequestID (ie. attempt)
+ //
+ int operationAttempt;
+ try {
+ operationAttempt = Integer
+ .parseInt(response.getCommonHeader().getSubRequestId());
+ } catch (NumberFormatException e) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ //
+ this.completeOperation(
+ this.getAttempt(),
+ "Policy was unable to parse APP-C SubRequestID (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Sanity check the response message
+ //
+ if (response.getStatus() == null) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ //
+ this.completeOperation(
+ operationAttempt,
+ "Policy was unable to parse APP-C response status field (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Get the Response Code
+ //
+ AppcLcmResponseCode responseValue = AppcLcmResponseCode
+ .toResponseValue(response.getStatus().getCode());
+ if (responseValue == null) {
+ //
+ // We are unaware of this code
+ //
+ this.completeOperation(
+ operationAttempt,
+ "Policy was unable to parse APP-C response status code field.",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Ok, let's figure out what APP-C's response is
+ //
+ switch (responseValue) {
+ case ACCEPTED:
+ //
+ // This is good, they got our original message and
+ // acknowledged it.
+ //
+ // Is there any need to track this?
+ //
+ return;
+ case ERROR:
+ case REJECT:
+ //
+ // We'll consider these two codes as exceptions
+ //
+ this.completeOperation(operationAttempt,
+ response.getStatus().getMessage(),
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ case FAILURE:
+ //
+ // APPC could not do a healthcheck
+ //
+ this.completeOperation(operationAttempt,
+ response.getStatus().getMessage(),
+ PolicyResult.FAILURE);
+ return;
+ case SUCCESS:
+ //
+ // This means APPC was able to execute the health check.
+ // The payload has to be parsed to see if the VNF is
+ // healthy or unhealthy
+ //
+
+ //
+ // sanity check the payload
+ //
+ if (response.getPayload() == null || response.getPayload().isEmpty()) {
+ //
+ // We are cannot parse the payload
+ //
+ this.completeOperation(
+ operationAttempt,
+ "Policy was unable to parse APP-C response payload because it was null.",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+
+ //
+ // parse the payload to see if the VNF is healthy/unhealthy
+ //
+ String vnfHealthState = getVnfHealthState(response.getPayload());
+ if ("healthy".equalsIgnoreCase(vnfHealthState)) {
+ this.completeOperation(operationAttempt, "VNF is healthy",
+ PolicyResult.SUCCESS);
+ } else if ("unhealthy".equalsIgnoreCase(vnfHealthState)) {
+ this.completeOperation(operationAttempt, "VNF is unhealthy",
+ PolicyResult.FAILURE);
+ } else {
+ this.completeOperation(
+ operationAttempt,
+ "Error: Could not determine the state of the VNF."
+ + " The state field in the APPC response payload was unrecognized or null.",
+ PolicyResult.FAILURE_EXCEPTION);
+ }
+ return;
+ default:
+ return;
+ }
+ }
+
+ /**
+ * This method will construct a payload for a health check.
+ * The payload must be an escaped json string so gson is used
+ * to convert the payload hashmap into json
+ *
+ * @return an escaped json string representation of the payload
+ * @throws ControlLoopException if it occurs
+ */
+ @Override
+ protected String setPayload(Map<String, String> aai, String recipe) throws ControlLoopException {
+ Map<String, String> payload = new HashMap<>();
+
+ // Extract oam ip address from the onset
+ String ipAddr = aai.get(DCAE_IPV4_ADDR);
+ if (ipAddr != null) {
+ payload.put("host-ip-address", ipAddr);
+ } else {
+ logger.error("Error - IPv4 Address not found in the onset");
+ setErrorStatus("Error - IPv4 Address not found in the onset");
+ }
+
+ try {
+ return coder.encode(payload);
+ } catch (CoderException e) {
+ return null;
+ }
+ }
+}
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java
new file mode 100644
index 000000000..6a2518f46
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java
@@ -0,0 +1,703 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * 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.appclcm;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+
+import org.onap.policy.appclcm.AppcLcmBody;
+import org.onap.policy.appclcm.AppcLcmCommonHeader;
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmInput;
+import org.onap.policy.appclcm.AppcLcmOutput;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.controlloop.policy.TargetType;
+import org.onap.policy.drools.m2.lock.LockAdjunct;
+import org.onap.policy.guard.PolicyGuardResponse;
+import org.onap.policy.m2.appclcm.model.AppcLcmResponseCode;
+import org.onap.policy.m2.base.GuardAdjunct;
+import org.onap.policy.m2.base.Operation;
+import org.onap.policy.m2.base.Transaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is used for all APPC LCM operations. The only difference between
+ * operation types (Restart, Rebuild, Migrate, Evacuate, or HealthCheck) as
+ * far as DroolsPDP is concerned, is the operation name (policy.recipe).
+ * It is up to APPC to interpret these operations.
+ */
+public class AppcLcmOperation implements Operation, LockAdjunct.Requestor, Serializable {
+ public static final String DCAE_CLOSEDLOOP_DISABLED_FIELD = "vserver.is-closed-loop-disabled";
+ public static final String DCAE_VSERVER_SELF_LINK_FIELD = "vserver.selflink";
+ public static final String DCAE_IDENTITY_FIELD = "cloud-region.identity-url";
+ public static final String DCAE_VNF_NAME_FIELD = "generic-vnf.vnf-name";
+ public static final String DCAE_VNF_ID_FIELD = "generic-vnf.vnf-id";
+ public static final String DCAE_VSERVER_ID_FIELD = "vserver.vserver-id";
+ public static final String DCAE_TENANT_ID_FIELD = "tenant.tenant-id";
+
+ public static final String APPC_LCM_VM_ID_FIELD = "vm-id";
+ public static final String APPC_LCM_IDENTITY_URL_FIELD = "identity-url";
+ public static final String APPC_LCM_TENANT_ID_FIELD = "tenant-id";
+
+ private static Logger logger = LoggerFactory.getLogger(AppcLcmOperation.class);
+
+ private static final long serialVersionUID = 5062964240000304989L;
+
+ // state when waiting for a lock
+ public static final String LCM_WAIT_FOR_LOCK = "LCM.WAIT_FOR_LOCK";
+
+ // state when waiting for a response from 'guard'
+ public static final String LCM_GUARD_PENDING = "LCM.GUARD_PENDING";
+
+ // state when ready to send out the LCM message
+ public static final String LCM_BEGIN = "LCM.BEGIN";
+
+ // state when waiting for a response from APPC
+ public static final String LCM_PENDING = "LCM.PENDING";
+
+ // state when processing can't continue due to errors
+ public static final String LCM_ERROR = "LCM.ERROR";
+
+ // state when the operation has completed (success, failure, or timeout)
+ public static final String LCM_COMPLETE = "LCM.COMPLETE";
+
+ // the APPC LCM recipes supported by Policy
+ private static final ImmutableList<String> recipes = ImmutableList.of(
+ "Restart", "Rebuild", "Migrate", "Evacuate",
+ "HealthCheck", "Reboot", "Start", "Stop");
+
+ // used for JSON <-> String conversion
+ protected static StandardCoder coder = new StandardCoder();
+
+ // current state -- one of the 6 values, above
+ @Getter(onMethod = @__({@Override}))
+ private String state;
+
+ // transaction associated with this operation
+ private Transaction transaction;
+
+ // policy associated with this operation
+ @Getter(onMethod = @__({@Override}))
+ private Policy policy;
+
+ // initial onset message
+ private VirtualControlLoopEvent onset;
+
+ // attempt associated with this operation
+ @Getter(onMethod = @__({@Override}))
+ private int attempt;
+
+ // message, if any, associated with the result of this operation
+ @Getter(onMethod = @__({@Override}))
+ private String message = null;
+
+ // operation result -- set to a non-null value when the operation completes
+ @Getter(onMethod = @__({@Override}))
+ private PolicyResult result = null;
+
+ // the APPC LCM 'target' derived from the onset
+ private String target;
+
+ // reference to a Transaction adjunct supporting guard operations
+ private GuardAdjunct guardAdjunct;
+
+ // counter for how many partial failures were received from appc
+ private int partialFailureCount = 0;
+
+ // counter for how many partial success were received from appc
+ private int partialSuccessCount = 0;
+
+ /**
+ * Constructor -- initialize an LCM operation instance,
+ * try to acquire a lock, and start the guard query if we are ready.
+ *
+ * @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
+ */
+ public AppcLcmOperation(Transaction transaction, Policy policy, ControlLoopEvent onset,
+ int attempt) {
+ // state prior to aquiring the lock
+ // (will be changed when the lock is acquired)
+ this.state = LCM_WAIT_FOR_LOCK;
+ this.transaction = transaction;
+ this.policy = policy;
+ this.attempt = attempt;
+
+ if (!(onset instanceof VirtualControlLoopEvent)) {
+ // we need the correct 'onset' event type
+ state = LCM_COMPLETE;
+ result = PolicyResult.FAILURE;
+ message = "Onset event has the wrong type";
+ return;
+ }
+
+ this.onset = (VirtualControlLoopEvent)onset;
+
+ // fetch or create the guard adjunct -- note that 'guard' operations are
+ // only performed if a 'GuardContext' is present, and the adjunct was
+ // created by the Drools rules prior to creating this operation
+ this.guardAdjunct = transaction.getAdjunct(GuardAdjunct.class);
+
+ // attempt to get a lock for the VM -- if we get it immediately,
+ // we can go to the 'LCM_GUARD_PENDING' or 'LCM_BEGIN' state
+
+ target = this.onset.getAai().get(onset.getTarget()).toString();
+ String key = onset.getTargetType() + ":" + target;
+ if (transaction.getAdjunct(LockAdjunct.class).getLock(this, key,
+ transaction.getRequestId().toString(), false)) {
+ // lock was acquired immediately -- move on to the 'guard query'
+ // phase
+ guardQuery();
+ }
+ }
+
+ /**
+ * A method returning true if the A&AI subtag exists
+ * and the control loop exists and is not disabled and
+ * the target field exists as a key in the A&AI subtag.
+ *
+ * @param transaction the transaction corresponding to an event
+ * @param event the onset containing the A&AI subtag
+ * @return true if the A&AI subtag is valid, false otherwise
+ */
+ public static boolean isAaiValid(Transaction transaction, VirtualControlLoopEvent event) {
+ if (event.getAai() == null) {
+ transaction.setNotificationMessage("No A&AI Subtag");
+ return false;
+ } else if (!event.getAai().containsKey(DCAE_CLOSEDLOOP_DISABLED_FIELD)) {
+ transaction.setNotificationMessage(DCAE_CLOSEDLOOP_DISABLED_FIELD
+ + " information missing");
+ return false;
+ } else if (isClosedLoopDisabled(event.getAai())) {
+ transaction.setNotificationMessage(DCAE_CLOSEDLOOP_DISABLED_FIELD
+ + " is set to true");
+ return false;
+ } else if (!event.getAai().containsKey(event.getTarget())) {
+ transaction.setNotificationMessage("target field invalid - must have corresponding AAI value");
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isClosedLoopDisabled(Map<String, String> map) {
+ if (!map.containsKey(DCAE_CLOSEDLOOP_DISABLED_FIELD)) {
+ return false;
+ }
+ String disabled = map.get(DCAE_CLOSEDLOOP_DISABLED_FIELD);
+ return ("true".equalsIgnoreCase(disabled) || "y".equalsIgnoreCase(disabled));
+ }
+
+ /**
+ * trigger an asynchronous guard query -- if guard is not enabled,
+ * we go directly to the 'LCM_BEGIN' state.
+ */
+ private void guardQuery() {
+ if (guardAdjunct.asyncQuery(policy, target, onset.getRequestId().toString())) {
+ // 'GuardContext' is available --
+ // wait for an incoming 'PolicyGuardResponse' message
+ this.state = LCM_GUARD_PENDING;
+ } else {
+ // no 'GuardContext' is available -- go directly to the 'begin' state
+ this.state = LCM_BEGIN;
+ transaction.modify();
+ }
+ }
+
+ /*=====================================*/
+ /* 'LockAdjunct.Requestor' interface */
+ /*=====================================*/
+
+ /**
+ * This method is called by 'LockAdjunct' if we initially had to wait for
+ * the lock, but it has now became available.
+ */
+ public void lockAvailable() {
+ if (this.state == LCM_WAIT_FOR_LOCK) {
+ // we have the lock -- invoke 'quardQuery()',
+ // go to the appropriate state, and mark the transaction as modified
+ guardQuery();
+
+ // the 'lockAvailable' method was presumably triggered by the
+ // release
+ // of the lock by an unrelated transaction -- 'transaction.modify'
+ // is
+ // called to let Drools know that our transaction has gone through a
+ // state change
+ transaction.modify();
+ }
+ }
+
+ /**
+ * This method is called by 'LockAdjunct' if the lock was unable to be
+ * obtained.
+ */
+ public void lockUnavailable() {
+ if (this.state == LCM_WAIT_FOR_LOCK) {
+ try {
+ setErrorStatus("Already processing event with this target");
+ } catch (ControlLoopException e) {
+ logger.debug("Lock could not be obtained for this operation");
+ }
+ }
+ }
+
+ /*=======================*/
+ /* 'Operation' interface */
+ /*=======================*/
+
+ /**
+ * This method maps the recipe to the correct rpc-name syntax.
+ */
+ private String toRpcName(String recipe) {
+ String rpcName = recipe.toLowerCase();
+ if ("healthcheck".equals(rpcName)) {
+ rpcName = "health-check";
+ }
+ return rpcName;
+ }
+
+ /**
+ * This method forwards the construction of the recipe's
+ * payload to the proper handler.
+ *
+ * @return a json representation of the payload
+ * @throws ControlLoopException if it occurs
+ */
+ protected String setPayload(Map<String, String> aai, String recipe) throws ControlLoopException {
+ Map<String, String> payload = null;
+
+ switch (recipe) {
+ case "restart":
+ case "rebuild":
+ case "migrate":
+ case "evacuate":
+ case "start":
+ case "stop":
+ if (this.policy.getTarget().getType() == TargetType.VM) {
+ payload = setCommonPayload(aai);
+ }
+ break;
+ case "reboot":
+ payload = setRebootPayload();
+ break;
+ default:
+ payload = null;
+ break;
+ }
+
+ if (payload == null) {
+ return null;
+ }
+
+ try {
+ return coder.encode(payload);
+ } catch (CoderException e) {
+ return null;
+ }
+ }
+
+ /**
+ * This method will construct a payload for a restart, rebuild,
+ * migrate, or evacuate. The payload must be an escaped json
+ * string so gson is used to convert the payload hashmap into
+ * json
+ *
+ * @return a hashmap representation of the payload
+ * @throws ControlLoopException if it occurs
+ */
+ private Map<String, String> setCommonPayload(Map<String, String> aai) throws ControlLoopException {
+ Map<String, String> payload = new HashMap<>();
+
+ for (Map.Entry<String, String> entry : aai.entrySet()) {
+ switch (entry.getKey()) {
+ case DCAE_VSERVER_SELF_LINK_FIELD:
+ if (entry.getValue() != null) {
+ payload.put(APPC_LCM_VM_ID_FIELD, entry.getValue());
+ } else {
+ setErrorStatus("dcae onset is missing " + DCAE_VSERVER_SELF_LINK_FIELD);
+ }
+ break;
+ case DCAE_IDENTITY_FIELD:
+ if (entry.getValue() != null) {
+ payload.put(APPC_LCM_IDENTITY_URL_FIELD, entry.getValue());
+ } else {
+ setErrorStatus("dcae onset is missing " + DCAE_IDENTITY_FIELD);
+ }
+ break;
+ case DCAE_TENANT_ID_FIELD:
+ if (entry.getValue() != null) {
+ payload.put(APPC_LCM_TENANT_ID_FIELD, entry.getValue());
+ } else {
+ setErrorStatus("dcae onset is missing " + DCAE_TENANT_ID_FIELD);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return payload;
+ }
+
+ /**
+ * This method will construct a payload for a reboot.
+ * The payload must be an escaped json string so gson is used
+ * to convert the payload hashmap into json. The reboot payload
+ * requires a type of "HARD" or "SOFT" reboot from the policy
+ * defined through CLAMP.
+ *
+ * @return an escaped json string representation of the payload
+ */
+ private Map<String, String> setRebootPayload() throws ControlLoopException {
+ Map<String, String> payload = new HashMap<>();
+
+ if (this.policy.getTarget().getType() == TargetType.VM) {
+ payload = setCommonPayload(onset.getAai());
+ // The tenant-id is not used for the reboot request so we can remove
+ // it after being added by the common payload
+ payload.remove(APPC_LCM_TENANT_ID_FIELD);
+ }
+
+ // Extract "HARD" or "SOFT" from YAML policy
+ String type = this.policy.getPayload().get("type").toUpperCase();
+ payload.put("type", type);
+
+ return payload;
+ }
+
+ /**
+ * Return the request message associated with this operation.
+ *
+ * {@inheritDoc}
+ *
+ * @throws ControlLoopException if it occurs
+ */
+ @Override
+ public Object getRequest() throws ControlLoopException {
+ AppcLcmCommonHeader commonHeader = new AppcLcmCommonHeader();
+ commonHeader.setRequestId(onset.getRequestId());
+ commonHeader.setOriginatorId("POLICY");
+ commonHeader.setSubRequestId(String.valueOf(attempt));
+
+ // Policy will send a default ttl of 10 minutes (600 seconds)
+ Map<String, String> flags = new HashMap<>();
+ flags.put("ttl", "600");
+ commonHeader.setFlags(flags);
+
+ String action = null;
+ for (String recipe: recipes) {
+ if (recipe.equalsIgnoreCase(policy.getRecipe())) {
+ action = recipe;
+ break;
+ }
+ }
+
+ if (action == null) {
+ setErrorStatus("Error - invalid recipe");
+ }
+
+ Map<String, String> actionIdentifiers = new HashMap<>();
+
+ // The vnf-id is needed for both VNF and VM level operations
+ if (onset.getAai().containsKey(DCAE_VNF_NAME_FIELD)) {
+ actionIdentifiers.put("vnf-id", onset.getAai().get(DCAE_VNF_ID_FIELD));
+ } else {
+ logger.error("Error - no AAI DCAE VNF NAME key in the onset");
+ setErrorStatus("Error - no VNF NAME key in the onset");
+ }
+
+ if (this.policy.getTarget().getType() == TargetType.VM) {
+ if (onset.getAai().containsKey(DCAE_VSERVER_ID_FIELD)) {
+ actionIdentifiers.put("vserver-id", onset.getAai().get(DCAE_VSERVER_ID_FIELD));
+ } else {
+ logger.error("Error - no DCAE VSERVER ID key in the onset AAI\n");
+ setErrorStatus("Error - no VSERVER ID key in the onset");
+ }
+ }
+
+ String payload = setPayload(onset.getAai(), action.toLowerCase());
+
+ // construct an APPC LCM 'Request' message
+ AppcLcmInput request = new AppcLcmInput();
+
+ request.setCommonHeader(commonHeader);
+ request.setAction(action);
+ request.setActionIdentifiers(actionIdentifiers);
+ request.setPayload(payload);
+
+ // Pass the LCM request to the LCM wrapper
+ AppcLcmDmaapWrapper dmaapWrapper = new AppcLcmDmaapWrapper();
+ dmaapWrapper.setVersion("2.0");
+ AppcLcmBody appcBody = new AppcLcmBody();
+ appcBody.setInput(request);
+ dmaapWrapper.setBody(appcBody);
+ dmaapWrapper.setCorrelationId(onset.getRequestId() + "-" + attempt);
+ dmaapWrapper.setRpcName(toRpcName(action));
+ dmaapWrapper.setType("request");
+
+ // go to the LCM_PENDING state, under the assumption that the
+ // calling Drools code will send out the message we are returning
+ this.state = LCM_PENDING;
+ transaction.modify();
+ return dmaapWrapper;
+ }
+
+ /**
+ * This method is called by 'incomingMessage' when the message is a
+ * 'PolicyGuardResponse' message (leaving 'incomingMessage' to focus on
+ * 'Response' messages).
+ *
+ * @param response the received guard response message
+ */
+ void incomingGuardMessage(PolicyGuardResponse response) {
+ // this message is only meaningful if we are waiting for a
+ // 'guard' response -- ignore it, if this isn't the case
+ if (this.state == LCM_GUARD_PENDING) {
+ if ("Deny".equals(response.getResult())) {
+ // this is a guard failure
+ logger.error("LCM operation denied by 'Guard'");
+ this.message = "Denied by Guard";
+ this.result = PolicyResult.FAILURE_GUARD;
+ this.state = LCM_COMPLETE;
+ } else {
+ // everything else is treated as 'Permit'
+ this.state = LCM_BEGIN;
+ transaction.modify();
+ }
+ }
+ }
+
+ /**
+ * An incoming message is being delivered to the operation.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void incomingMessage(Object object) {
+ if (! (object instanceof AppcLcmDmaapWrapper)) {
+ if (object instanceof PolicyGuardResponse) {
+ incomingGuardMessage((PolicyGuardResponse)object);
+ return;
+ } else if (object instanceof ControlLoopEvent) {
+ incomingAbatedEvent((ControlLoopEvent) object);
+ return;
+ }
+ // ignore this message (not sure why we even got it)
+ return;
+ }
+
+ // If we reach this point, we have a 'AppcLcmDmaapWrapper' instance.
+ // The rest of this method is mostly copied from
+ // 'ControlLoopOperationManager.onResponse'.
+
+ AppcLcmOutput response = ((AppcLcmDmaapWrapper)object).getBody().getOutput();
+
+ //
+ // Determine which subrequestID (ie. attempt)
+ //
+ int operationAttempt;
+ try {
+ operationAttempt = Integer.parseInt(response.getCommonHeader()
+ .getSubRequestId());
+ } catch (NumberFormatException e) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ // If the attempt cannot be parsed then we assume it is
+ // the current attempt
+ //
+ this.completeOperation(this.attempt, "Policy was unable to parse APP-C SubRequestID (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Sanity check the response message
+ //
+ if (response.getStatus() == null) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ //
+ this.completeOperation(operationAttempt,
+ "Policy was unable to parse APP-C response status field (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Get the Response Code
+ //
+ AppcLcmResponseCode responseValue = AppcLcmResponseCode.toResponseValue(response.getStatus().getCode());
+ if (responseValue == null) {
+ //
+ // We are unaware of this code
+ //
+ this.completeOperation(operationAttempt, "Policy was unable to parse APP-C response status code field.",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Ok, let's figure out what APP-C's response is
+ //
+ switch (responseValue) {
+ case ACCEPTED:
+ //
+ // This is good, they got our original message and
+ // acknowledged it.
+ //
+ // Is there any need to track this?
+ //
+ return;
+ case PARTIAL_SUCCESS:
+ //
+ // Keep count of partial successes to determine
+ // if retries should be done at the vnf level
+ //
+ this.partialSuccessCount++;
+ return;
+ case PARTIAL_FAILURE:
+ //
+ // Keep count of partial failures to determine
+ // if no retries should be done
+ //
+ this.partialFailureCount++;
+ return;
+ case ERROR:
+ case REJECT:
+ //
+ // We'll consider these two codes as exceptions
+ //
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.FAILURE_EXCEPTION);
+ return;
+ case SUCCESS:
+ //
+ //
+ //
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.SUCCESS);
+ return;
+ case FAILURE:
+ // For the VNF level operations, retries will be attempted only
+ // if ALL individual VMs failed the operation
+ if (this.partialSuccessCount == 0) {
+ // Since there are no partial successes, that means all VMs failed
+ // if all vms fail, we can retry the VNF level action
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.FAILURE);
+ } else if (this.partialFailureCount > 0) {
+ // Since only a subset of VMs had partial failures,
+ // the result should go to final failure and not
+ // retry or move on to the next policy in the chain.
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.FAILURE_EXCEPTION);
+ }
+ return;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * This method is called by 'incomingMessage' only when an 'ABATED' event is received before an APPC
+ * request is sent.
+ *
+ * @param event the control loop event that was received
+ */
+ private void incomingAbatedEvent(ControlLoopEvent event) {
+ // check if ClosedLoopEventStatus is 'abated'
+ if (event.isEventStatusValid() && "ABATED".equalsIgnoreCase(event.getClosedLoopEventStatus().toString())) {
+ this.result = PolicyResult.SUCCESS;
+ this.message = "Abatement received before APPC request was sent";
+ this.state = LCM_COMPLETE;
+ }
+ }
+
+ /**
+ * This method is called by 'incomingMessage' in order to complete the
+ * operation.
+ *
+ * @param attempt the operation attempt indicated in the response message
+ * @param message the value to store in the 'message' field'
+ * @param result the value to store in the 'result' field
+ */
+ void completeOperation(int attempt, String message, PolicyResult result) {
+ logger.debug("LCM: completeOperation("
+ + "this.attempt=" + this.attempt
+ + ", attempt=" + attempt
+ + ", result=" + result
+ + ", message=" + message);
+ if (this.attempt == attempt) {
+ // we need to verify that the attempt matches in order to reduce the
+ // chances that we are reacting to a prior 'Response' message that
+ // was received after we timed out (unfortunately, we can't guarantee
+ // this, because we have no reliable way to verify the 'recipe')
+
+ this.message = message;
+ this.result = result;
+ state = LCM_COMPLETE;
+ }
+ }
+
+ /**
+ * The operation has timed out.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void timeout() {
+ result = PolicyResult.FAILURE_TIMEOUT;
+ state = LCM_COMPLETE;
+ }
+
+ void setErrorStatus(String message) throws ControlLoopException {
+ result = PolicyResult.FAILURE_EXCEPTION;
+ state = LCM_ERROR;
+ this.message = message;
+ transaction.modify();
+ throw new ControlLoopException(message);
+ }
+
+ /**
+ * This is called right after it's history entry has been completed.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void histEntryCompleted(ControlLoopOperation histEntry) {
+ // give 'guard' a chance to create a DB entry (this only happens if
+ // we really have a 'GuardContext', and all of the needed parameters
+ // were provided in the '*-controller.properties' file)
+ guardAdjunct.asyncCreateDbEntry(histEntry, target);
+ }
+}
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java
new file mode 100644
index 000000000..ca812a4db
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java
@@ -0,0 +1,58 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * 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.appclcm.model;
+
+import java.io.Serializable;
+
+public enum AppcLcmResponseCode implements Serializable {
+ ACCEPTED, ERROR, REJECT, SUCCESS, FAILURE, PARTIAL_SUCCESS, PARTIAL_FAILURE;
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Translates the code to a string value that represents the meaning of the
+ * code.
+ *
+ * @param code
+ * the numeric value that is returned by APPC based on success,
+ * failure, etc. of the action requested
+ * @return the enum value equivalent of the APPC response code
+ */
+ public static AppcLcmResponseCode toResponseValue(int code) {
+ if (code == 100) {
+ return ACCEPTED;
+ } else if (code == 200) {
+ return ERROR;
+ } else if (code >= 300 && code <= 316) {
+ return REJECT;
+ } else if (code == 400) {
+ return SUCCESS;
+ } else if (code == 450 || (code >= 401 && code <= 406)) {
+ return FAILURE;
+ } else if (code == 500) {
+ return PARTIAL_SUCCESS;
+ } else if (code >= 501 && code <= 599) {
+ return PARTIAL_FAILURE;
+ }
+ return null;
+ }
+
+}
diff --git a/controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor b/controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor
new file mode 100644
index 000000000..2e6065608
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor
@@ -0,0 +1 @@
+org.onap.policy.m2.appclcm.AppcLcmActor
diff --git a/controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmHealthCheckOperationTest.java b/controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmHealthCheckOperationTest.java
new file mode 100644
index 000000000..0748b6ee0
--- /dev/null
+++ b/controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmHealthCheckOperationTest.java
@@ -0,0 +1,281 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * 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 appclcm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import java.util.Properties;
+import java.util.UUID;
+
+import org.drools.core.WorkingMemory;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmInput;
+import org.onap.policy.appclcm.util.Serialization;
+import org.onap.policy.controlloop.ControlLoopEventStatus;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopTargetType;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.controlloop.policy.Target;
+import org.onap.policy.controlloop.policy.TargetType;
+import org.onap.policy.drools.m2.lock.LockAdjunct;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.m2.appclcm.AppcLcmHealthCheckOperation;
+import org.onap.policy.m2.base.Transaction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppcLcmHealthCheckOperationTest {
+ private static Logger logger = LoggerFactory.getLogger(AppcLcmHealthCheckOperationTest.class);
+
+ public static Policy policy;
+ public static VirtualControlLoopEvent event;
+ public static Transaction transaction;
+ public static AppcLcmHealthCheckOperation operation;
+
+ /**
+ * Class-level setup.
+ */
+ @BeforeClass
+ public static void setup() {
+ PolicyEngineConstants.getManager().configure(new Properties());
+ PolicyEngineConstants.getManager().start();
+
+ policy = new Policy();
+ policy.setActor("APPCLCM");
+ policy.setTarget(new Target(TargetType.VM));
+
+ event = new VirtualControlLoopEvent();
+ event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET);
+ event.setRequestId(UUID.randomUUID());
+ event.setTarget("vserver.vserver-name");
+ event.setTargetType(ControlLoopTargetType.VM);
+ 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.vnf-id", "0f551f1b-e4e5-4ce2-84da-eda916e06e1c");
+ 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", "https://compute-aic.dpa3.cci.att.com:8774/");
+ event.getAai().put("vserver.prov-status", "PROV");
+ event.getAai().put("complex.physical-location-id", "LSLEILAA");
+
+ WorkingMemory wm = mock(WorkingMemory.class);
+ transaction = new Transaction(wm, "clvusptest", event.getRequestId(), null);
+
+ }
+
+ @AfterClass
+ public static void cleanup() {
+ transaction.cleanup();
+ PolicyEngineConstants.getManager().stop();
+ }
+
+ @Test
+ public void getVnfHealthCheckRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("health-check", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("HealthCheck", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertNotNull(appcRequest.getPayload());
+ assertTrue(appcRequest.getPayload().contains("host-ip-address"));
+
+ logger.info("health-check request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void incomingHealthCheckMessageHealthyStateTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":400,\"message\":\"HealthCheckSuccessful\"},"
+ + "\"payload\":\"{\\\"identifier\\\":\\\"scoperepresented\\\",\\\"state\\\":\\\"healthy\\\","
+ + "\\\"time\\\":\\\"01-01-1000:0000\\\"}\"}},\"version\":\"2.0\",\"rpc-name\":\"health-check\","
+ + "\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\",\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.SUCCESS);
+ }
+
+ @Test
+ public void incomingHealthCheckMessageUnhealthyStateTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":400,\"message\":\"VNF is unhealthy\"},"
+ + "\"payload\":\"{\\\"identifier\\\":\\\"scoperepresented\\\",\\\"state\\\":\\\"unhealthy\\\","
+ + "\\\"info\\\":\\\"Systemthresholdexceededdetails\\\",\\\"fault\\\":{\\\"cpuOverall\\\":0.80,"
+ + "\\\"cpuThreshold\\\":0.45},\\\"time\\\":\\\"01-01-1000:0000\\\"}\"}},\"version\":\"2.0\","
+ + "\"rpc-name\":\"health-check\",\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\","
+ + "\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE);
+ }
+
+ @Test
+ public void incomingHealthCheckMessageUnknownStateTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":400,\"message\":\"VNF is unhealthy\"},"
+ + "\"payload\":\"{\\\"identifier\\\":\\\"scoperepresented\\\",\\\"state\\\":\\\"unknown\\\","
+ + "\\\"info\\\":\\\"Systemthresholdexceededdetails\\\",\\\"fault\\\":{\\\"cpuOverall\\\":0.80,"
+ + "\\\"cpuThreshold\\\":0.45},\\\"time\\\":\\\"01-01-1000:0000\\\"}\"}},\"version\":\"2.0\","
+ + "\"rpc-name\":\"health-check\",\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\","
+ + "\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE_EXCEPTION);
+ }
+
+ @Test
+ public void incomingHealthCheckMessageNoStateTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":400,\"message\":\"VNF is unhealthy\"},"
+ + "\"payload\":\"{\\\"identifier\\\":\\\"scoperepresented\\\","
+ + "\\\"info\\\":\\\"Systemthresholdexceededdetails\\\",\\\"fault\\\":{\\\"cpuOverall\\\":0.80,"
+ + "\\\"cpuThreshold\\\":0.45},\\\"time\\\":\\\"01-01-1000:0000\\\"}\"}},\"version\":\"2.0\","
+ + "\"rpc-name\":\"health-check\",\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\","
+ + "\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE_EXCEPTION);
+ }
+
+ @Test
+ public void incomingHealthCheckMessageUnsuccessfulTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":401,\"message\":\"Could not complete HealthCheck\"},"
+ + "\"payload\":\"{\\\"identifier\\\":\\\"scoperepresented\\\","
+ + "\\\"info\\\":\\\"Systemthresholdexceededdetails\\\",\\\"fault\\\":{\\\"cpuOverall\\\":0.80,"
+ + "\\\"cpuThreshold\\\":0.45},\\\"time\\\":\\\"01-01-1000:0000\\\"}\"}},\"version\":\"2.0\","
+ + "\"rpc-name\":\"health-check\",\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\","
+ + "\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE);
+ }
+
+ @Test
+ public void incomingHealthCheckMessageNoPayloadTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":400,\"message\":\"VNF is unhealthy\"}}},\"version\":\"2.0\","
+ + "\"rpc-name\":\"health-check\",\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\","
+ + "\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE_EXCEPTION);
+ }
+
+ @Test
+ public void incomingHealthCheckMessageEmptyPayloadTest() {
+ policy.setRecipe("HEALTHCHECK");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmHealthCheckOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"body\":{\"output\":{\"common-header\":{\"timestamp\":\"2017-08-25T21:06:23.037Z\","
+ + "\"api-ver\":\"5.00\",\"originator-id\":\"POLICY\","
+ + "\"request-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200\",\"sub-request-id\":\"1\",\"flags\":{}},"
+ + "\"status\":{\"code\":400,\"message\":\"VNF is unhealthy\"},\"payload\":\"\"}},\"version\":\"2.0\","
+ + "\"rpc-name\":\"health-check\",\"correlation-id\":\"664be3d2-6c12-4f4b-a3e7-c349acced200-1\","
+ + "\"type\":\"response\"}";
+ AppcLcmDmaapWrapper healthCheckResp = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(healthCheckResp);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE_EXCEPTION);
+ }
+}
diff --git a/controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmOperationTest.java b/controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmOperationTest.java
new file mode 100644
index 000000000..0ddfb5b2c
--- /dev/null
+++ b/controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmOperationTest.java
@@ -0,0 +1,708 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * 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 appclcm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.UUID;
+
+import org.drools.core.WorkingMemory;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmInput;
+import org.onap.policy.appclcm.util.Serialization;
+import org.onap.policy.controlloop.ControlLoopEventStatus;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopTargetType;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.controlloop.policy.Target;
+import org.onap.policy.controlloop.policy.TargetType;
+import org.onap.policy.drools.m2.lock.LockAdjunct;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.m2.appclcm.AppcLcmOperation;
+import org.onap.policy.m2.base.Transaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppcLcmOperationTest {
+ private static Logger logger = LoggerFactory.getLogger(AppcLcmOperationTest.class);
+
+ public static Policy policy;
+ public static VirtualControlLoopEvent event;
+ public static Transaction transaction;
+ public static AppcLcmOperation operation;
+
+ /**
+ * Class-level setup.
+ */
+ @BeforeClass
+ public static void start() {
+ PolicyEngineConstants.getManager().configure(new Properties());
+ PolicyEngineConstants.getManager().start();
+
+ policy = new Policy();
+ policy.setActor("APPCLCM");
+ policy.setTarget(new Target(TargetType.VM));
+
+ event = new VirtualControlLoopEvent();
+ event.setClosedLoopEventStatus(ControlLoopEventStatus.ONSET);
+ event.setRequestId(UUID.randomUUID());
+ event.setTarget("vserver.vserver-name");
+ event.setTargetType(ControlLoopTargetType.VM);
+ 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.vnf-id", "0f551f1b-e4e5-4ce2-84da-eda916e06e1c");
+ 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", "https://compute-aic.dpa3.cci.att.com:8774/");
+ event.getAai().put("vserver.prov-status", "PROV");
+ event.getAai().put("complex.physical-location-id", "LSLEILAA");
+
+ WorkingMemory wm = mock(WorkingMemory.class);
+ transaction = new Transaction(wm, "clvusptest", event.getRequestId(), null);
+ }
+
+ @AfterClass
+ public static void cleanup() {
+ transaction.cleanup();
+ PolicyEngineConstants.getManager().stop();
+ }
+
+ @Test
+ public void getVmRestartRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("restart", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Restart", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm restart request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfRestartRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("restart", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Restart", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNull(appcRequest.getPayload());
+
+ logger.info("vnf restart request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVmRebuildRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("REBUILD");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("rebuild", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Rebuild", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm rebuild request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfRebuildRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("REBUILD");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("rebuild", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Rebuild", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNull(appcRequest.getPayload());
+
+ logger.info("vnf rebuild request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVmMigrateRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("MIGRATE");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("migrate", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Migrate", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm migrate request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfMigrateRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("MIGRATE");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("migrate", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Migrate", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNull(appcRequest.getPayload());
+
+ logger.info("vnf migrate request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVmEvacuateRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("EVACUATE");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("evacuate", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Evacuate", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm evacuate request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfEvacuateRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("EVACUATE");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("evacuate", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Evacuate", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNull(appcRequest.getPayload());
+
+ logger.info("vnf evacuate request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVmRebootRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("REBOOT");
+ policy.getTarget().setType(TargetType.VM);
+ policy.setPayload(new HashMap<String, String>());
+ policy.getPayload().put("type", "HARD");
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("reboot", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Reboot", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm reboot request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfRebootRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("REBOOT");
+ policy.getTarget().setType(TargetType.VNF);
+ policy.setPayload(new HashMap<String, String>());
+ policy.getPayload().put("type", "HARD");
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("reboot", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Reboot", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vnf reboot request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfStartRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("START");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("start", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Start", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNull(appcRequest.getPayload());
+
+ logger.info("vnf start request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVmStartRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("START");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("start", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Start", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm start request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVnfStopRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("STOP");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+ assertEquals("stop", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Stop", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(appcRequest.getActionIdentifiers().get("vserver-id"), null);
+ assertNull(appcRequest.getPayload());
+
+ logger.info("vnf stop request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ @Test
+ public void getVmStopRequestTest() throws ControlLoopException {
+
+ policy.setRecipe("STOP");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ Object request = operation.getRequest();
+ assertTrue(request instanceof AppcLcmDmaapWrapper);
+
+ AppcLcmDmaapWrapper dmaapRequest = (AppcLcmDmaapWrapper) request;
+ assertEquals("request", dmaapRequest.getType());
+
+ assertEquals("stop", dmaapRequest.getRpcName());
+ assertNotNull(dmaapRequest.getBody());
+
+ AppcLcmInput appcRequest = dmaapRequest.getBody().getInput();
+ assertNotNull(appcRequest.getCommonHeader());
+ assertEquals("2.00", appcRequest.getCommonHeader().getApiVer());
+ assertEquals("POLICY", appcRequest.getCommonHeader().getOriginatorId());
+ assertNotNull(appcRequest.getAction());
+ assertEquals("Stop", appcRequest.getAction());
+ assertNotNull(appcRequest.getActionIdentifiers());
+ assertEquals(event.getAai().get("generic-vnf.vnf-id"), appcRequest.getActionIdentifiers().get("vnf-id"));
+ assertEquals(event.getAai().get("vserver.vserver-id"), appcRequest.getActionIdentifiers().get("vserver-id"));
+ assertNotNull(appcRequest.getPayload());
+
+ logger.info("vm stop request: {}", Serialization.gson.toJson(request, AppcLcmDmaapWrapper.class));
+
+ }
+
+ /* ===================================================================== */
+
+ /*
+ * these tests are for ensuring the incoming response messages process
+ * properly and translate to the expected policy result
+ */
+
+ @Test
+ public void incomingVmSuccessMessageTest() {
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"version\": \"2.0\",\"rpc-name\": \"Restart\",\"correlation-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064-1\",\"type\": \"response\",\"body\": {\"output\": {\"status\": {\"code\": 400,\"message\": \"Restart Successful\"},\"common-header\": {\"timestamp\": \"2017-07-18T16:52:06.186Z\",\"api-ver\": \"2.01\",\"request-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064\",\"sub-request-id\": \"1\",\"flags\": {\"ttl\": 600}},\"payload\": \"{\\\"vm-id\\\":\\\"http://135.25.246.131:8774/v2/81fc2bc61f974de1b5a49e8c2ec090bb/servers/75dce20c-97f9-454d-abcc-aa904a33df5a\\\",\\\"tenant-id\\\":\\\"test2\\\"}\"}}}";
+ AppcLcmDmaapWrapper restartResponse = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), PolicyResult.SUCCESS);
+ }
+
+ @Test
+ public void incomingVnfSuccessMessageTest() {
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"version\": \"2.0\",\"rpc-name\": \"Restart\",\"correlation-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064-1\",\"type\": \"response\",\"body\": {\"output\": {\"status\": {\"code\": 500,\"message\": \"Restart Successful\"},\"common-header\": {\"timestamp\": \"2017-07-18T16:52:06.186Z\",\"api-ver\": \"2.01\",\"request-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064\",\"sub-request-id\": \"1\",\"flags\": {\"ttl\": 600}},\"payload\": \"{\\\"vm-id\\\":\\\"http://135.25.246.131:8774/v2/81fc2bc61f974de1b5a49e8c2ec090bb/servers/75dce20c-97f9-454d-abcc-aa904a33df5a\\\",\\\"tenant-id\\\":\\\"test2\\\"}\"}}}";
+ AppcLcmDmaapWrapper restartResponse = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ /* Send in several partial success messages */
+ for (int i = 0; i < 5; i++) {
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), null);
+ }
+
+ /* Send in an operation success */
+ restartResponse.getBody().getOutput().getStatus().setCode(400);
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), PolicyResult.SUCCESS);
+ }
+
+ @Test
+ public void incomingVmFailureMessageTest() {
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VM);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"version\": \"2.0\",\"rpc-name\": \"Restart\",\"correlation-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064-1\",\"type\": \"response\",\"body\": {\"output\": {\"status\": {\"code\": 401,\"message\": \"Restart Successful\"},\"common-header\": {\"timestamp\": \"2017-07-18T16:52:06.186Z\",\"api-ver\": \"2.01\",\"request-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064\",\"sub-request-id\": \"1\",\"flags\": {\"ttl\": 600}},\"payload\": \"{\\\"vm-id\\\":\\\"http://135.25.246.131:8774/v2/81fc2bc61f974de1b5a49e8c2ec090bb/servers/75dce20c-97f9-454d-abcc-aa904a33df5a\\\",\\\"tenant-id\\\":\\\"test2\\\"}\"}}}";
+ AppcLcmDmaapWrapper restartResponse = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), PolicyResult.FAILURE);
+ }
+
+ @Test
+ public void incomingAllVnfFailureMessageTest() {
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"version\": \"2.0\",\"rpc-name\": \"Restart\",\"correlation-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064-1\",\"type\": \"response\",\"body\": {\"output\": {\"status\": {\"code\": 501,\"message\": \"Restart Successful\"},\"common-header\": {\"timestamp\": \"2017-07-18T16:52:06.186Z\",\"api-ver\": \"2.01\",\"request-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064\",\"sub-request-id\": \"1\",\"flags\": {\"ttl\": 600}},\"payload\": \"{\\\"vm-id\\\":\\\"http://135.25.246.131:8774/v2/81fc2bc61f974de1b5a49e8c2ec090bb/servers/75dce20c-97f9-454d-abcc-aa904a33df5a\\\",\\\"tenant-id\\\":\\\"test2\\\"}\"}}}";
+ AppcLcmDmaapWrapper restartResponse = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ /* Send in ALL failure messages */
+ for (int i = 0; i < 5; i++) {
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), null);
+ }
+
+ /* Send in an operation failure */
+ restartResponse.getBody().getOutput().getStatus().setCode(401);
+ operation.incomingMessage(restartResponse);
+
+ /* Because every VM failed in the VNF, it should be failure result */
+ assertEquals(operation.getResult(), PolicyResult.FAILURE);
+ }
+
+ @Test
+ public void incomingPartialVnfFailureMessageTest() {
+ policy.setRecipe("RESTART");
+ policy.getTarget().setType(TargetType.VNF);
+ operation = new AppcLcmOperation(transaction, policy, event, 1);
+
+ String lcmRespJson = "{\"version\": \"2.0\",\"rpc-name\": \"Restart\",\"correlation-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064-1\",\"type\": \"response\",\"body\": {\"output\": {\"status\": {\"code\": 500,\"message\": \"Restart Successful\"},\"common-header\": {\"timestamp\": \"2017-07-18T16:52:06.186Z\",\"api-ver\": \"2.01\",\"request-id\": \"baf5ba32-6b8c-430c-a91b-02d2c0ba3064\",\"sub-request-id\": \"1\",\"flags\": {\"ttl\": 600}},\"payload\": \"{\\\"vm-id\\\":\\\"http://135.25.246.131:8774/v2/81fc2bc61f974de1b5a49e8c2ec090bb/servers/75dce20c-97f9-454d-abcc-aa904a33df5a\\\",\\\"tenant-id\\\":\\\"test2\\\"}\"}}}";
+ AppcLcmDmaapWrapper restartResponse = Serialization.gson.fromJson(lcmRespJson, AppcLcmDmaapWrapper.class);
+
+ /* Send in several partial success messages */
+ for (int i = 0; i < 5; i++) {
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), null);
+ }
+
+ /* Change status to partial failure */
+ restartResponse.getBody().getOutput().getStatus().setCode(501);
+
+ /* Send in several partial failures messages */
+ for (int i = 0; i < 5; i++) {
+ operation.incomingMessage(restartResponse);
+ assertEquals(operation.getResult(), null);
+ }
+
+ /* Send in an operation failure */
+ restartResponse.getBody().getOutput().getStatus().setCode(401);
+ operation.incomingMessage(restartResponse);
+
+ /*
+ * Only a subset of VMs failed in the VNF so the
+ * result will be failure_exception
+ */
+ assertEquals(operation.getResult(), PolicyResult.FAILURE_EXCEPTION);
+ }
+
+ /* ===================================================================== */
+
+ /*
+ * these tests are for validating the A&AI subtag and target in an onset
+ */
+
+ @Test
+ public void validAaiSubtagTest() {
+ transaction.setNotificationMessage(null);
+ VirtualControlLoopEvent validEvent = new VirtualControlLoopEvent();
+ validEvent.setTarget("vserver.vserver-name");
+ validEvent.getAai().put(AppcLcmOperation.DCAE_CLOSEDLOOP_DISABLED_FIELD, "false");
+ validEvent.getAai().put(validEvent.getTarget(), "VM001");
+ assertTrue(AppcLcmOperation.isAaiValid(transaction, validEvent));
+ assertNull(transaction.getNotificationMessage());
+ }
+
+ @Test
+ public void noAaiSubtagTest() {
+ transaction.setNotificationMessage(null);
+ VirtualControlLoopEvent noAaiTag = new VirtualControlLoopEvent();
+ noAaiTag.setAai(null);
+ assertFalse(AppcLcmOperation.isAaiValid(transaction, noAaiTag));
+ assertEquals(transaction.getNotificationMessage(), "No A&AI Subtag");
+ }
+
+ @Test
+ public void noClosedLoopDisabledInAaiTest() {
+ transaction.setNotificationMessage(null);
+ VirtualControlLoopEvent invalidEvent = new VirtualControlLoopEvent();
+ assertFalse(AppcLcmOperation.isAaiValid(transaction, invalidEvent));
+ assertEquals(AppcLcmOperation.DCAE_CLOSEDLOOP_DISABLED_FIELD
+ + " information missing", transaction.getNotificationMessage());
+ }
+
+ @Test
+ public void closedLoopDisabledInAaiTest() {
+ transaction.setNotificationMessage(null);
+ VirtualControlLoopEvent invalidEvent = new VirtualControlLoopEvent();
+ invalidEvent.getAai().put(AppcLcmOperation.DCAE_CLOSEDLOOP_DISABLED_FIELD, "true");
+ assertFalse(AppcLcmOperation.isAaiValid(transaction, invalidEvent));
+ assertEquals(AppcLcmOperation.DCAE_CLOSEDLOOP_DISABLED_FIELD
+ + " is set to true", transaction.getNotificationMessage());
+ }
+
+ @Test
+ public void targetMismatchInAaiTest() {
+ transaction.setNotificationMessage(null);
+ VirtualControlLoopEvent validEvent = new VirtualControlLoopEvent();
+ validEvent.setTarget("vserver.vserver-name");
+ validEvent.getAai().put(AppcLcmOperation.DCAE_CLOSEDLOOP_DISABLED_FIELD, "false");
+ assertFalse(AppcLcmOperation.isAaiValid(transaction, validEvent));
+ assertEquals("target field invalid - must have corresponding AAI value",
+ transaction.getNotificationMessage());
+ }
+}
diff --git a/controlloop/m2/appclcm/src/test/java/model/AppcLcmResponseCodeTest.java b/controlloop/m2/appclcm/src/test/java/model/AppcLcmResponseCodeTest.java
new file mode 100644
index 000000000..fc3349253
--- /dev/null
+++ b/controlloop/m2/appclcm/src/test/java/model/AppcLcmResponseCodeTest.java
@@ -0,0 +1,44 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * appclcm
+ * ================================================================================
+ * 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 model;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.junit.Test;
+
+import org.onap.policy.m2.appclcm.model.AppcLcmResponseCode;
+
+
+public class AppcLcmResponseCodeTest {
+
+ @Test
+ public void test() {
+ assertEquals(AppcLcmResponseCode.ACCEPTED, AppcLcmResponseCode.toResponseValue(100));
+ assertEquals(AppcLcmResponseCode.ERROR, AppcLcmResponseCode.toResponseValue(200));
+ assertEquals(AppcLcmResponseCode.REJECT, AppcLcmResponseCode.toResponseValue(300));
+ assertEquals(AppcLcmResponseCode.SUCCESS, AppcLcmResponseCode.toResponseValue(400));
+ assertEquals(AppcLcmResponseCode.FAILURE, AppcLcmResponseCode.toResponseValue(450));
+ assertEquals(AppcLcmResponseCode.PARTIAL_SUCCESS, AppcLcmResponseCode.toResponseValue(500));
+ assertEquals(AppcLcmResponseCode.PARTIAL_FAILURE, AppcLcmResponseCode.toResponseValue(501));
+ assertNull(AppcLcmResponseCode.toResponseValue(600));
+ }
+}
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 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>base</artifactId>
+ <name>Experimental Control Loop Model - base</name>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>guard</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.common</groupId>
+ <artifactId>eventmanager</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+ <artifactId>model-yaml</artifactId>
+ <version>${policy.models.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>events</artifactId>
+ <version>${policy.models.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>aai</artifactId>
+ <version>${policy.models.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions.model-impl</groupId>
+ <artifactId>sdc</artifactId>
+ <version>${policy.models.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
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);
+ }
+}
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<Policy> 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");
+ }
+}
diff --git a/controlloop/m2/feature-controlloop-m2/pom.xml b/controlloop/m2/feature-controlloop-m2/pom.xml
new file mode 100644
index 000000000..7cedfa3dc
--- /dev/null
+++ b/controlloop/m2/feature-controlloop-m2/pom.xml
@@ -0,0 +1,126 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>feature-controlloop-m2</artifactId>
+
+ <build>
+ <resources>
+ <resource>
+ <directory>src/main/feature</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>zipfile</id>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <phase>package</phase>
+ <configuration>
+ <attach>true</attach>
+ <finalName>${project.artifactId}-${project.version}</finalName>
+ <descriptors>
+ <descriptor>src/assembly/assemble_zip.xml</descriptor>
+ </descriptors>
+ <appendAssemblyId>false</appendAssemblyId>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-dependencies</id>
+ <goals>
+ <goal>copy-dependencies</goal>
+ </goals>
+ <phase>prepare-package</phase>
+ <configuration>
+ <outputDirectory>${project.build.directory}/assembly/lib</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ <overWriteIfNewer>true</overWriteIfNewer>
+ <useRepositoryLayout>false</useRepositoryLayout>
+ <addParentPoms>false</addParentPoms>
+ <copyPom>false</copyPom>
+ <includeScope>runtime</includeScope>
+ <excludeTransitive>true</excludeTransitive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>guard</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>base</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>appclcm</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>lock</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>adapters</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/controlloop/m2/feature-controlloop-m2/src/assembly/assemble_zip.xml b/controlloop/m2/feature-controlloop-m2/src/assembly/assemble_zip.xml
new file mode 100644
index 000000000..bf006dd1a
--- /dev/null
+++ b/controlloop/m2/feature-controlloop-m2/src/assembly/assemble_zip.xml
@@ -0,0 +1,80 @@
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<!-- Defines how we build the .zip file which is our distribution. -->
+
+<assembly
+ xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
+ <id>feature-controlloop-m2-package</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <includeBaseDirectory>false</includeBaseDirectory>
+
+ <fileSets>
+ <fileSet>
+ <directory>target</directory>
+ <outputDirectory>lib/feature</outputDirectory>
+ <includes>
+ <include>feature-controlloop-m2-${project.version}.jar</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>target/assembly/lib</directory>
+ <outputDirectory>lib/dependencies</outputDirectory>
+ <includes>
+ <include>*.jar</include>
+ </includes>
+ </fileSet>
+
+ <fileSet>
+ <directory>target/classes/config</directory>
+ <outputDirectory>config</outputDirectory>
+ <fileMode>0644</fileMode>
+ <excludes/>
+ </fileSet>
+
+ <fileSet>
+ <directory>src/main/feature/bin</directory>
+ <outputDirectory>bin</outputDirectory>
+ <fileMode>0755</fileMode>
+ <excludes/>
+ </fileSet>
+
+ <fileSet>
+ <directory>src/main/feature/db</directory>
+ <outputDirectory>db</outputDirectory>
+ <fileMode>0755</fileMode>
+ <excludes/>
+ </fileSet>
+
+ <fileSet>
+ <directory>src/main/feature/install</directory>
+ <outputDirectory>install</outputDirectory>
+ <fileMode>0755</fileMode>
+ <excludes/>
+ </fileSet>
+ </fileSets>
+
+</assembly>
diff --git a/controlloop/m2/guard/pom.xml b/controlloop/m2/guard/pom.xml
new file mode 100644
index 000000000..501151a85
--- /dev/null
+++ b/controlloop/m2/guard/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>guard</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>2.13.0</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>util</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-core</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.common</groupId>
+ <artifactId>guard</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.common</groupId>
+ <artifactId>database</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java b/controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java
new file mode 100644
index 000000000..d0d1b831f
--- /dev/null
+++ b/controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java
@@ -0,0 +1,400 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * guard
+ * ================================================================================
+ * 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.guard;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.function.Supplier;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+import org.drools.core.WorkingMemory;
+
+import org.onap.policy.database.operationshistory.Dbao;
+import org.onap.policy.drools.controller.DroolsController;
+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;
+import org.onap.policy.guard.Util;
+import org.onap.policy.util.DroolsSessionCommonSerializable;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Each instance of this class is initialized from a 'Properties' object,
+ * which is most likely a '*-controller.properties' file. The expectation is
+ * that it will be initialized within a '.drl' file, and be referenced by
+ * the associated 'Context' object.
+ */
+public class GuardContext implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private static final String ECLIPSE_LINK_KEY_DRIVER = "javax.persistence.jdbc.driver";
+
+ private static Logger logger = LoggerFactory.getLogger(GuardContext.class);
+
+ // object that should be serialized
+ private Object namedSerializable;
+
+ /*==================================*/
+ /* fields extracted from properties */
+ /*==================================*/
+ // contains the four database properties, 'javax.persistence.jdbc.*',
+ private Properties dbProperties = null;
+
+ // initialized from 'guard.disabled', but may also be set to 'true' if
+ // there is an initialization error
+ private boolean disabled = false;
+
+ // errors that forced 'disabled' to be set to 'true'
+ private String errorMessage = null;
+
+ /*======================================================*/
+ /* fields that shouldn't be included in serialized data */
+ /*======================================================*/
+
+ // derived from DB properties
+ private transient EntityManagerFactory emf = null;
+
+ /**
+ * Constructor - initialize the 'GuardContext' instance using the
+ * controller's properties file. The properties file is located using a
+ * 'PolicySession' instance, but the way this mapping is done isn't
+ * perfect -- it may use the wrong properties file if there is another
+ * 'PolicyContainer' instance using the same 'artifactId' and 'groupId'.
+ *
+ * @param session the 'PolicySession' instance used to locate the associated
+ * 'Properties' instance
+ */
+ public GuardContext(PolicySession session) {
+ this(session, null);
+ }
+
+ /**
+ * Constructor - initialize the 'GuardContext' instance using the
+ * controller's properties file. The properties file is located using a
+ * 'PolicySession' instance, but the way this mapping is done isn't
+ * perfect -- it may use the wrong properties file if there is another
+ * 'PolicyContainer' instance using the same 'artifactId' and 'groupId'.
+ *
+ * @param session the 'PolicySession' instance used to locate the associated
+ * 'Properties' instance
+ * @param serializableName a String name unique within the Drools session
+ * that can be used to locate the corresponding 'GuardContext' object
+ * on the remote host
+ */
+ public GuardContext(PolicySession session, String serializableName) {
+ namedSerializable =
+ (serializableName == null ? this :
+ new DroolsSessionCommonSerializable(serializableName, this));
+
+ // At present, there is no simple way to get the properties based
+ // upon a 'PolicyContainer'. Instead, we search through all of the
+ // 'PolicyController' instances looking for one with a matching
+ // 'artifactId' and 'groupId'. Note that this may not work correctly
+ // if there is more than one controller using the same or different
+ // version of the same artifact.
+
+ PolicyContainer container = session.getPolicyContainer();
+ String artifactId = container.getArtifactId();
+ String groupId = container.getGroupId();
+
+ Properties properties =
+ PolicyControllerConstants.getFactory().get(groupId, artifactId).getProperties();
+ init(properties);
+ }
+
+ /**
+ * Constructor - initialize the 'GuardContext' instance using the
+ * specified properties.
+ *
+ * @param properties configuration data used to initialize the 'GuardContext' instance
+ */
+ public GuardContext(Properties properties) {
+ init(properties);
+ }
+
+ /**
+ * Common initialization routine code used by both constructors.
+ *
+ * @param properties configuration data used to initialize the 'GuardContext' instance
+ */
+ private void init(Properties properties) {
+ // used to store error messages
+ StringBuilder sb = new StringBuilder();
+
+ // fetch these parameters, if they exist
+ String disabledString =
+ PolicyEngineConstants.getManager().getEnvironmentProperty(Util.PROP_GUARD_DISABLED);
+
+ if (disabledString != null) {
+ // decode optional 'guard.disabled' parameter
+ disabled = Boolean.valueOf(disabledString);
+ if (disabled) {
+ // skip everything else
+ return;
+ }
+ }
+
+ // extract 'guard.java.persistence.jdbc.*' parameters,
+ // which are all mandatory
+ dbProperties = new Properties();
+ setProperty(dbProperties, Util.ONAP_KEY_URL, Util.ECLIPSE_LINK_KEY_URL, sb);
+ setProperty(dbProperties, Util.ONAP_KEY_USER, Util.ECLIPSE_LINK_KEY_USER, sb);
+ setProperty(dbProperties, Util.ONAP_KEY_PASS, Util.ECLIPSE_LINK_KEY_PASS, sb);
+ String driver = properties.getProperty("guard." + ECLIPSE_LINK_KEY_DRIVER);
+ if (driver != null) {
+ dbProperties.setProperty(ECLIPSE_LINK_KEY_DRIVER, driver);
+ }
+
+ // if there are any errors, update 'errorMessage' & disable guard queries
+ if (sb.length() != 0) {
+ // remove the terminating ", ", and extract resulting error message
+ sb.setLength(sb.length() - 2);
+ errorMessage = sb.toString();
+ disabled = true;
+ logger.error("Initialization failure: " + errorMessage);
+ }
+ }
+
+ /**
+ * Fetch a property from the PolicyEngine environment, and store it in
+ * a corresponding property in 'properties'.
+ *
+ * @param properties the location to store the properties
+ * @param srcName source environment property name
+ * @param destName destination property name
+ * @param log a 'StringBuilder' used to construct an error message, if needed
+ */
+ private void setProperty(Properties properties, String srcName, String destName, StringBuilder log) {
+ String value =
+ PolicyEngineConstants.getManager().getEnvironmentProperty(srcName);
+ if (value == null) {
+ log.append("'").append(srcName).append("' is not defined, ");
+ } else {
+ properties.setProperty(destName, value);
+ }
+ }
+
+ /**
+ * Do an asynchronous (non-blocking) HTTP REST query to see if this
+ * operation is permitted by 'guard'. The response is returned by
+ * inserting a 'PolicyGuardResponse' instance into Drools memory.
+ *
+ * @param workingMemory the Drools response is inserted here
+ * @param actor the processor being acted upon (e.g. "APPC")
+ * @param recipe otherwise known as "operation" (e.g. "Restart")
+ * @param target a further qualifier on 'actor'? (e.g. "VM")
+ * @param requestId the UUID string identifying the overall request
+ */
+ public void asyncQuery(
+ WorkingMemory workingMemory,
+ String actor, String recipe, String target,
+ String requestId) {
+
+ asyncQuery(workingMemory, actor, recipe, target, requestId, null);
+ }
+
+ /**
+ * Do an asynchronous (non-blocking) HTTP REST query to see if this
+ * operation is permitted by 'guard'. The response is returned by
+ * inserting a 'PolicyGuardResponse' instance into Drools memory.
+ *
+ * @param workingMemory the Drools response is inserted here
+ * @param actor the processor being acted upon (e.g. "APPC")
+ * @param recipe otherwise known as "operation" (e.g. "Restart")
+ * @param target a further qualifier on 'actor'? (e.g. "VM")
+ * @param requestId the UUID string identifying the overall request
+ * @param controlLoopName the 'controlLoopName' value or 'null'
+ * (if 'null', it is ommitted from the query to 'guard')
+ */
+ public void asyncQuery(
+ final WorkingMemory workingMemory,
+ final String actor, final String recipe, final String target,
+ final String requestId, final String controlLoopName) {
+
+ if (disabled) {
+ logger.error("query skipped: {}", errorMessage);
+ workingMemory.insert(
+ new PolicyGuardResponse("Deny", UUID.fromString(requestId), recipe));
+ return;
+ }
+
+ CallGuardTask cgt = new CallGuardTask(workingMemory, controlLoopName,
+ actor, recipe, target, requestId, () -> null);
+
+ PolicyEngineConstants.getManager().getExecutorService().execute(cgt);
+ }
+
+ /**
+ * Create an 'EntityManagerFactory', if needed, and then create a new
+ * 'EntityManager' instance.
+ *
+ * @return a new 'EntityManager' instance
+ */
+ private EntityManager createEntityManager() {
+ if (emf == null) {
+ // 'EntityManagerFactory' does not exist yet -- create one
+
+ // copy database properties to a 'HashMap'
+ HashMap<Object,Object> propertiesMap = new HashMap<>(dbProperties);
+
+ // use 'ClassLoader' from Drools session
+ propertiesMap.put("eclipselink.classloader",
+ GuardContext.class.getClassLoader());
+
+ // create DB tables, if needed
+ propertiesMap.put("eclipselink.ddl-generation", "create-tables");
+
+ // create entity manager factory
+ emf = Persistence.createEntityManagerFactory("OperationsHistoryPU", propertiesMap);
+ }
+
+ // create and return the 'EntityManager'
+ return emf.createEntityManager();
+ }
+
+ /**
+ * This is a synchronous (blocking) method, which creates a database entity
+ * for an in-progress request.
+ *
+ * @param starttime this is used as the 'starttime' timestamp in the record
+ * @param endtime this is used as the 'endtime' timestamp in the record
+ * @param closedLoopControlName uniquely identifies the Drools rules
+ * @param actor the processor being acted upon (e.g. "APPC")
+ * @param recipe otherwise known as "operation" (e.g. "Restart")
+ * @param target a further qualifier on 'actor'? (e.g. "VM")
+ * @param requestId the UUID string identifying the overall request
+ * @param subRequestId further qualifier on 'requestId'
+ * @param message indicates success status, or reason for failure
+ * @param outcome 'PolicyResult' enumeration string
+ * @return 'true' if the operation was successful, and 'false' if not
+ */
+ public boolean createDbEntry(
+ Instant starttime, Instant endtime, String closedLoopControlName,
+ String actor, String recipe, String target,
+ String requestId, String subRequestId, String message, String outcome) {
+
+ if (disabled) {
+ if (errorMessage != null) {
+ logger.error("Database update skipped: " + errorMessage);
+ }
+ return false;
+ }
+
+ EntityManager em = null;
+ boolean rval = false;
+
+ try {
+ em = createEntityManager();
+
+ // create the new DB table entry
+ Dbao newEntry = new Dbao();
+
+ // populate the new DB table entry
+ newEntry.setClosedLoopName(closedLoopControlName);
+ newEntry.setRequestId(requestId);
+ newEntry.setActor(actor);
+ newEntry.setOperation(recipe);
+ newEntry.setTarget(target);
+ newEntry.setStarttime(new Timestamp(starttime.toEpochMilli()));
+ newEntry.setSubrequestId(subRequestId);
+
+ newEntry.setEndtime(new Timestamp(endtime.toEpochMilli()));
+ newEntry.setMessage(message);
+ newEntry.setOutcome(outcome);
+
+ // store the new entry in the DB
+ em.getTransaction().begin();
+ em.persist(newEntry);
+ em.getTransaction().commit();
+
+ rval = true;
+ } finally {
+ // free EntityManager
+ if (em != null) {
+ em.close();
+ }
+ }
+ return rval;
+ }
+
+ /**
+ * This is an asynchronous (non-blocking) method, which creates a database
+ * entity for an in-progress request.
+ *
+ * @param starttime this is used as the 'starttime' timestamp in the record
+ * @param endtime this is used as the 'endtime' timestamp in the record
+ * @param closedLoopControlName uniquely identifies the Drools rules
+ * @param actor the processor being acted upon (e.g. "APPC")
+ * @param recipe otherwise known as "operation" (e.g. "Restart")
+ * @param target a further qualifier on 'actor'? (e.g. "VM")
+ * @param requestId the UUID string identifying the overall request
+ * @param subRequestId further qualifier on 'requestId'
+ * @param message indicates success status, or reason for failure
+ * @param outcome 'PolicyResult' enumeration string
+ */
+ public void asyncCreateDbEntry(
+ final Instant starttime, final Instant endtime,
+ final String closedLoopControlName,
+ final String actor, final String recipe, final String target,
+ final String requestId, final String subRequestId,
+ final String message, final String outcome) {
+ if (disabled) {
+ if (errorMessage != null) {
+ logger.error("Database update skipped: " + errorMessage);
+ }
+ return;
+ }
+
+ PolicyEngineConstants.getManager().getExecutorService().execute(() -> {
+ try {
+ // using a separate thread, call the synchronous 'createDbEntry'
+ // method
+ createDbEntry(starttime, endtime, closedLoopControlName,
+ actor, recipe, target, requestId, subRequestId,
+ message, outcome);
+ } catch (Exception e) {
+ logger.error("GuardContext.asyncCreateDbEntry", e);
+ }
+ });
+ }
+
+ /**
+ * This method is used as part of serialization -- 'namedSerializable'
+ * is serialized instead of 'this'.
+ *
+ * @return the object to be serialized
+ */
+ private Object writeReplace() throws ObjectStreamException {
+ return namedSerializable;
+ }
+}
diff --git a/controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java b/controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java
new file mode 100644
index 000000000..1a61d9019
--- /dev/null
+++ b/controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java
@@ -0,0 +1,139 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * guard
+ * ================================================================================
+ * 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.guard;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.drools.core.WorkingMemory;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+
+public class GuardContextTest {
+
+ private static Properties prop;
+ private static GuardContext guardContext;
+ private static WorkingMemory workingMemory;
+ private static LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>();
+
+ /**
+ * Class-level initialization.
+ */
+ @BeforeClass
+ public static void setup() throws IOException {
+ PolicyEngineConstants.getManager().configure(new Properties());
+ PolicyEngineConstants.getManager().start();
+
+ prop = new Properties();
+ prop.setProperty("guard.pdp.rest.url", "http://www.google.com/");
+ prop.setProperty("guard.pdp.rest.client.user", "testuser");
+ prop.setProperty("guard.pdp.rest.client.password", "testpassword");
+ prop.setProperty("guard.pdp.rest.timeout", "1000");
+ prop.setProperty("guard.pdp.rest.environment", "dev");
+
+ workingMemory = mock(WorkingMemory.class);
+ when(workingMemory.insert(isNotNull())).thenAnswer(
+ invocation -> {
+ queue.add(invocation.getArgument(0));
+ return null;
+ });
+ }
+
+ @AfterClass
+ public static void stop() {
+ PolicyEngineConstants.getManager().stop();
+ }
+
+ @Test
+ public void guardDbResponseTest() throws InterruptedException {
+ Properties props = new Properties(prop);
+ props.setProperty("guard.disabled", "false");
+ props.setProperty("guard.javax.persistence.jdbc.user", "user");
+ props.setProperty("guard.javax.persistence.jdbc.password", "secret");
+ props.setProperty("guard.javax.persistence.jdbc.driver", "org.h2.Driver");
+ props.setProperty("guard.javax.persistence.jdbc.url", "jdbc:h2:file:./H2DB");
+
+ guardContext = new GuardContext(props);
+ assertNotNull(guardContext);
+
+ guardContext.asyncCreateDbEntry(Instant.now().minusSeconds(1), Instant.now(),
+ "testCLName", "testActor", "testRecipe", "testTarget",
+ UUID.randomUUID().toString(), "1", "testMessage", "testOutcome");
+
+ queue.clear();
+ guardContext.asyncQuery(workingMemory, "testActor", "testRecipe",
+ "testTarget", UUID.randomUUID().toString(), "testCLName");
+ Object response = queue.poll(10, TimeUnit.SECONDS);
+ assertNotNull(response);
+ }
+
+ @Test
+ public void badValuesTest() throws InterruptedException {
+ Properties props = new Properties(prop);
+ props.setProperty("guard.disabled", "true");
+ props.setProperty("guard.pdp.rest.client.user", "");
+ props.setProperty("guard.pdp.rest.client.password", "");
+ props.setProperty("guard.pdp.rest.url", "bad,testuser,testpassword");
+
+ guardContext = new GuardContext(props);
+
+ guardContext.asyncCreateDbEntry(Instant.now().minusSeconds(1), Instant.now(),
+ "testCLName", "testActor", "testRecipe", "testTarget",
+ UUID.randomUUID().toString(), "1", "testMessage", "testOutcome");
+
+ queue.clear();
+ guardContext.asyncQuery(workingMemory, "testActor", "testRecipe",
+ "testTarget", UUID.randomUUID().toString());
+ Object response = queue.poll(10, TimeUnit.SECONDS);
+ assertNotNull(response);
+ }
+
+ @Test
+ public void policyGuardResponseTest() {
+ UUID requestId = UUID.randomUUID();
+ PolicyGuardResponse emptyResponse1 = new PolicyGuardResponse(null, null, null);
+
+ assertNotNull(emptyResponse1);
+
+ PolicyGuardResponse response = new PolicyGuardResponse("Some Result", requestId, "Some Details");
+
+ response.setRequestId(requestId);
+ assertEquals(requestId, response.getRequestId());
+
+ response.setResult("Some Result");
+ assertEquals("Some Result", response.getResult());
+
+ assertEquals("PolicyGuardResponse [requestId=", response.toString().substring(0, 31));
+ }
+}
diff --git a/controlloop/m2/lock/pom.xml b/controlloop/m2/lock/pom.xml
new file mode 100644
index 000000000..00ea2a84c
--- /dev/null
+++ b/controlloop/m2/lock/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>m2</artifactId>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>lock</artifactId>
+ <name>lock</name>
+ <description>Generic Lock used for Locking Target Entities</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>base</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/controlloop/m2/lock/src/main/java/org/onap/policy/drools/m2/lock/LockAdjunct.java b/controlloop/m2/lock/src/main/java/org/onap/policy/drools/m2/lock/LockAdjunct.java
new file mode 100644
index 000000000..e81ab6288
--- /dev/null
+++ b/controlloop/m2/lock/src/main/java/org/onap/policy/drools/m2/lock/LockAdjunct.java
@@ -0,0 +1,164 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/lock
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.drools.m2.lock;
+
+import java.io.Serializable;
+import java.util.HashMap;
+
+import org.onap.policy.drools.core.lock.Lock;
+import org.onap.policy.drools.core.lock.LockCallback;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.m2.base.Transaction;
+
+/*
+ * Adjunct data placed within the transaction, to contain locks
+ * on control loop use case basis. This same instance is shared
+ * among all actor operation instances within the transaction
+ * regardless of what actor is in the policy chain. As a result,
+ * the lock gets passed from one to the next, and is only freed
+ * when the transaction completes.
+ */
+public class LockAdjunct implements Transaction.Adjunct, LockCallback,
+ Serializable {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * This is the callback interface, which is only used if the lock is
+ * initially not available, and we end up waiting for it.
+ */
+ public interface Requestor {
+ /**
+ * This method is called once the lock has been acquired.
+ */
+ void lockAvailable();
+
+ /**
+ * This method is called to notify the requestor that the lock could not
+ * be obtained.
+ */
+ void lockUnavailable();
+ }
+
+ // lock associated with all of the AOTS and SDNR operations
+ // within the transaction
+ private Lock lock = null;
+
+ // the initial requestor of the lock
+ // (only set if we don't immediately acquire the lock)
+ private Requestor requestor = null;
+
+ /**
+ * allocate a lock on behalf of the requestor.
+ *
+ * @param requestor
+ * the AOTS or SDNR operation requesting the lock
+ * @param key
+ * string key identifying the lock
+ * @param ownerKey
+ * string key identifying the owner
+ * @param waitForLock
+ * used to determine if an operation should wait for a lock to
+ * become available or fail immediately
+ * @return 'true' if we immediately acquired the lock, 'false' if the lock
+ * is currently in use by another transaction, and we need to wait
+ */
+ public boolean getLock(Requestor requestor, String key, String ownerKey,
+ boolean waitForLock) {
+ if (lock != null) {
+ if (lock.isActive()) {
+ // we already have an active lock
+ return true;
+ }
+
+ // We may have timed out earlier waiting for the lock, or
+ // we may have lost the lock after persistent restore. In
+ // any case, free the lock we have now, and allocate a new one.
+ lock.free();
+ }
+
+ // register the requestor in case the lockUnavailable() callback runs
+ // immediately. Previously the requestor would be set after the
+ // lock constructor ran but now the lockUnavailable() callback
+ // could be executed while the constructor is still on the stack which
+ // would result in a null requestor, thus the callback never
+ // notifying the requestor that the lock was unavailable.
+ this.requestor = requestor;
+
+ // try to allocate a new lock
+ if ((lock = PolicyEngineConstants.getManager().createLock(
+ key, ownerKey, 600, this, waitForLock)).isActive()) {
+ // the lock is good
+ return true;
+ }
+
+ // we need to wait for the lock -- return false,
+ return false;
+ }
+
+ /*=================================*/
+ /* 'Transaction.Adjunct' interface */
+ /*=================================*/
+
+ /**
+ * Notification that the transaction has completed.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void cleanup(Transaction transaction) {
+ if (lock != null) {
+ // free the lock or cancel the reservation
+ lock.free();
+ }
+ }
+
+ /*==========================*/
+ /* 'LockCallback' interface */
+ /*==========================*/
+
+ /**
+ * Notification that the lock is available.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void lockAvailable(Lock lock) {
+ this.lock = lock;
+ if (requestor != null) {
+ // notify requestor
+ requestor.lockAvailable();
+ }
+ }
+
+ /**
+ * Notification that the lock is not available.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void lockUnavailable(Lock lock) {
+ this.lock = lock;
+ if (requestor != null) {
+ // notify requestor
+ requestor.lockUnavailable();
+ }
+ }
+}
diff --git a/controlloop/m2/lock/src/test/java/org/onap/policy/drools/m2/lock/LockAdjunctTest.java b/controlloop/m2/lock/src/test/java/org/onap/policy/drools/m2/lock/LockAdjunctTest.java
new file mode 100644
index 000000000..4c5770fb2
--- /dev/null
+++ b/controlloop/m2/lock/src/test/java/org/onap/policy/drools/m2/lock/LockAdjunctTest.java
@@ -0,0 +1,97 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/lock
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.drools.m2.lock;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Properties;
+import java.util.UUID;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.onap.policy.drools.core.lock.Lock;
+import org.onap.policy.drools.core.lock.LockCallback;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.m2.base.Transaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LockAdjunctTest {
+
+ private static Logger logger = LoggerFactory.getLogger(LockAdjunctTest.class);
+
+ public class TestOwner implements LockCallback {
+
+ @Override
+ public void lockAvailable(Lock arg0) {
+ return;
+ }
+
+ @Override
+ public void lockUnavailable(Lock arg0) {
+ return;
+ }
+ }
+
+ private LockCallback owner;
+ private Lock lock;
+
+ @BeforeClass
+ public static void start() {
+ PolicyEngineConstants.getManager().configure(new Properties());
+ PolicyEngineConstants.getManager().start();
+ }
+
+ @AfterClass
+ public static void stop() {
+ PolicyEngineConstants.getManager().stop();
+ }
+
+ @Test
+ public void lockAdjunctTest() {
+ owner = new TestOwner();
+ lock = PolicyEngineConstants.getManager().createLock("key", "ownerKey", 60, owner, false);
+ LockAdjunct lockA = new LockAdjunct();
+
+ assertNotNull(lockA);
+
+ lockA.lockUnavailable(lock);
+ assertTrue(lock.isActive());
+ assertTrue(lockA.getLock(null, "key", "ownerKey", false));
+ LockAdjunct lockB = new LockAdjunct();
+ assertFalse(lockB.getLock(null, "key", "ownerKey", false));
+ assertTrue(lock.free());
+
+ lockB.lockAvailable(lock);
+ assertFalse(lock.isActive());
+ assertTrue(lockB.getLock(null, "key", "ownerKey", false));
+ assertFalse(lock.free());
+
+ UUID uuid = UUID.randomUUID();
+ Transaction transaction = new Transaction(null, "1111", uuid, null);
+ lockA.cleanup(transaction);
+ }
+}
diff --git a/controlloop/m2/pom.xml b/controlloop/m2/pom.xml
new file mode 100644
index 000000000..8a0a4001f
--- /dev/null
+++ b/controlloop/m2/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop</groupId>
+ <artifactId>controlloop</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+
+ <name>M2 Control Loop Model</name>
+ <packaging>pom</packaging>
+
+ <modules>
+ <module>util</module>
+ <module>guard</module>
+ <module>base</module>
+ <module>appclcm</module>
+ <module>lock</module>
+ <module>adapters</module>
+ <module>test</module>
+ <module>feature-controlloop-m2</module>
+ </modules>
+
+</project>
diff --git a/controlloop/m2/test/pom.xml b/controlloop/m2/test/pom.xml
new file mode 100644
index 000000000..c389be5c7
--- /dev/null
+++ b/controlloop/m2/test/pom.xml
@@ -0,0 +1,100 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>test</artifactId>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <configuration>
+ <systemPropertyVariables>
+ <project.version>${project.version}</project.version>
+ </systemPropertyVariables>
+ </configuration>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-core</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-management</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>feature-drools-init</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.models.policy-models-interactions</groupId>
+ <artifactId>model-yaml</artifactId>
+ <version>${policy.models.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>appclcm</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/AppcLcmTest.java b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/AppcLcmTest.java
new file mode 100644
index 000000000..3a8cffb31
--- /dev/null
+++ b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/AppcLcmTest.java
@@ -0,0 +1,318 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/test
+ * ================================================================================
+ * 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.test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.onap.policy.guard.Util.ONAP_KEY_PASS;
+import static org.onap.policy.guard.Util.ONAP_KEY_URL;
+import static org.onap.policy.guard.Util.ONAP_KEY_USER;
+import static org.onap.policy.guard.Util.PROP_GUARD_URL;
+import static org.onap.policy.m2.test.Util.assertSubset;
+import static org.onap.policy.m2.test.Util.json;
+
+import com.google.gson.JsonObject;
+
+import java.io.File;
+import java.util.Properties;
+import java.util.UUID;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import org.onap.policy.drools.system.PolicyController;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.drools.util.KieUtils;
+import org.onap.policy.drools.utils.PropertyUtil;
+import org.onap.policy.m2.test.Util.Input;
+import org.onap.policy.m2.test.Util.Output;
+
+public class AppcLcmTest {
+ private static String closedLoopControlName = null;
+ private static Output dcae = null;
+ private static Output appcResponse = null;
+ private static Input notification = null;
+ private static Input appcRequest = null;
+ private static Properties properties = null;
+ private static PolicyController policyController = null;
+
+ /**
+ * Initialization method, which creates the following:
+ * 1) VUSPLCM artifact
+ * 2) The associated PolicyController and Drools session
+ * 3) DMAAP/UEB topic writers and readers
+ * .
+ */
+ @BeforeClass
+ public static void init() throws Exception {
+ Util.commonInit();
+
+ String projectVersion = System.getProperty("project.version");
+ assertNotNull(projectVersion);
+ closedLoopControlName = "appclcm-" + UUID.randomUUID().toString();
+
+ File kmodule = new File("src/test/resources/appclcm/kmodule.xml");
+
+ String pom = Util.openAndReplace("src/test/resources/appclcm/pom.xml",
+ "${project.version}", projectVersion);
+
+ String yaml = Util.fileToString(new File("src/test/resources/appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml"));
+
+ // build a '.drl' file (as a String), by replacing '${variable}' names
+ String drl = Util.openAndReplace(
+ "src/test/resources/appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl",
+ "${closedLoopControlName}", closedLoopControlName,
+ "${controlLoopYaml}", Util.convertYaml(yaml),
+ "${notificationTopic}", "NOTIFICATION-APPCLCM-TOPIC",
+ "${operationTopic}", "APPC-REQUEST-APPCLCM-TOPIC",
+ "${policyName}", "appclcm",
+ "${policyScope}", "service=vUSP;resource=vCTS;type=operational" ,
+ "${policyVersion}",
+ "org.onap.policy.m2.test:appclcm:" + projectVersion,
+ "${unique}", "2");
+
+ // this creates the JAR file, and installs it in the local repository
+ KieUtils.installArtifact(kmodule, Util.stringToFile(pom, ".xml"),
+ "src/main/resources/rules/rules.drl", Util.stringToFile(drl, ".drl"));
+
+ properties = PropertyUtil.getProperties("src/test/resources/appclcm/controller.properties");
+ properties.setProperty("rules.version", projectVersion);
+ //properties.setProperty("pdpx.username", "");
+ //properties.setProperty("pdpx.password", "");
+
+ // create PolicyController, which creates the Drools session
+ PolicyEngineConstants.getManager().setEnvironmentProperty(PROP_GUARD_URL, "http://127.0.71.201:8443/pdp/");
+ PolicyEngineConstants.getManager().setEnvironmentProperty(ONAP_KEY_URL, "jdbc:h2:file:./H2DB");
+ PolicyEngineConstants.getManager().setEnvironmentProperty(ONAP_KEY_USER, "sa");
+ PolicyEngineConstants.getManager().setEnvironmentProperty(ONAP_KEY_PASS, "");
+ policyController =
+ PolicyEngineConstants.getManager().createPolicyController("appclcm", properties);
+ policyController.start();
+
+ // create writers
+ dcae = new Output("org.onap.DCAE-APPCLCM-TOPIC");
+ appcResponse = new Output("APPC-RESPONSE-APPCLCM-TOPIC");
+
+ // create readers
+ notification = new Input("NOTIFICATION-APPCLCM-TOPIC");
+ appcRequest = new Input("APPC-REQUEST-APPCLCM-TOPIC");
+ }
+
+ /**
+ * Clean up.
+ */
+ @AfterClass
+ public static void cleanup() {
+ // close readers
+ notification.close();
+ appcRequest.close();
+
+ // close writers
+ dcae.close();
+ appcResponse.close();
+
+ // shut down PolicyController and Drools session
+ policyController.stop();
+ PolicyEngineConstants.getManager().stop();
+
+ // clean up REST servers
+ Util.commonShutdown();
+ }
+
+ /**
+ * This is a sunny-day scenario.
+ */
+ @Test
+ public void sunnyDayTest() throws Exception {
+ Request req = new Request();
+
+ // send initial ONSET message
+ dcae.send(req.msg);
+
+ // receive active notification, and restart operation
+ assertSubset(json("notification", "ACTIVE"),
+ notification.poll());
+
+ appcOperation(req, "Restart", 400, "Restart Successful");
+
+ // send ABATED
+ req.msg.addProperty("closedLoopEventStatus", "ABATED");
+ dcae.send(req.msg);
+
+ // receive final success notification
+ assertSubset(json("notification", "FINAL: SUCCESS"),
+ notification.poll());
+
+ // sleep to allow DB update
+ Thread.sleep(1000);
+ }
+
+ /**
+ * In this scenario, all requests fail until the final 'Evacuate'.
+ */
+ @Test
+ public void initialFailure() throws Exception {
+ Request req = new Request();
+
+ // send initial ONSET message
+ dcae.send(req.msg);
+
+ // active notification, and restart 1 operation
+ assertSubset(json("notification", "ACTIVE"),
+ notification.poll());
+
+ appcOperation(req, "Restart", 450, "Restart 1 Failed");
+ appcOperation(req, "Restart", 450, "Restart 2 Failed");
+ appcOperation(req, "Rebuild", 450, "Rebuild Failed");
+ appcOperation(req, "Migrate", 450, "Migrate Failed");
+ appcOperation(req, "Evacuate", 400, "Evacuate Successful");
+
+ // send ABATED
+ req.msg.addProperty("closedLoopEventStatus", "ABATED");
+ dcae.send(req.msg);
+
+ // receive final success notification
+ assertSubset(json("notification", "FINAL: SUCCESS"),
+ notification.poll());
+
+ // sleep to allow DB update
+ Thread.sleep(1000);
+ }
+
+ private void appcOperation(Request req, String name, int responseCode, String responseMessage)
+ throws Exception {
+ String lcName = name.toLowerCase();
+ assertSubset(json("notification", "OPERATION",
+ "message", ".*operation=" + name + ",.*"),
+ notification.poll());
+
+ // receive request
+ JsonObject opRequest = appcRequest.poll();
+ assertSubset(json("version", "2.0",
+ "rpc-name", lcName,
+ "correlation-id", ".*",
+ "type", "request",
+ "body",
+ json("input", json("common-header",
+ json("request-id", req.requestId),
+ "action", name
+ ))),
+ opRequest);
+
+ // send response
+ JsonObject ch = opRequest
+ .getAsJsonObject("body")
+ .getAsJsonObject("input")
+ .getAsJsonObject("common-header");
+ JsonObject opResponse =
+ json("correlation-id", opRequest.get("correlation-id"),
+ "body", json("output",
+ json("common-header",
+ json("flags", json(),
+ "api-ver", "2.00",
+ "originator-id", ch.get("originator-id"),
+ "sub-request-id", ch.get("sub-request-id"),
+ "request-id", req.requestId,
+ "timestamp", ch.get("timestamp")
+ ),
+ "status",
+ json("code", responseCode,
+ "message", responseMessage
+ ))),
+ "type", "response",
+ "version", "2.0",
+ "rpc-name", lcName
+ );
+ appcResponse.send(opResponse);
+
+ // receive success or failure notification
+ String expectedNotification =
+ (responseCode == 400 ? "OPERATION: SUCCESS" : "OPERATION: FAILURE");
+ assertSubset(json("notification", expectedNotification,
+ "message", ".*operation=" + name + ",.*"),
+ notification.poll());
+ }
+
+ /* ============================================================ */
+
+ /**
+ * An instance of this class is created for each Transaction. It allocates
+ * any identifiers, such as 'requestId', and creates the initial ONSET
+ * message.
+ */
+ class Request {
+ String requestId;
+ String triggerId;
+ JsonObject msg;
+
+ Request() {
+ long time = System.currentTimeMillis();
+ requestId = UUID.randomUUID().toString();
+ triggerId = "trigger-" + time;
+
+ msg = json("closedLoopEventClient", "configuration.dcae.microservice.stringmatcher.xml",
+ "policyVersion", "1610",
+ "triggerSourceName", "ctsf0002vm014",
+ "policyName", "vUSP_vCTS_CL_7.Config_MS_ClosedLoop_"
+ + "104b1445_6b30_11e7_852e_0050568c4ccf_StringMatch_1wo2qh0",
+ "policyScope", "resource=F5,service=vSCP,type=configuration,"
+ + "closedLoopControlName=vSCP_F5_Firewall_d925ed73-8231-4d02-9545-db4e101f88f8",
+ "triggerID", triggerId,
+ "target_type", "VM",
+ "AAI",
+ json("vserver.l-interface.l3-interface-ipv6-address-list.l3-inteface-ipv6-address", null,
+ "vserver.selflink", "https://compute-aic.dpa3.cci.att.com:8774/v2/d0719b845a804b368f8ac0bba39e188b/servers/7953d05b-6698-4aa6-87bd-39bed606133a",
+ "vserver.is-closed-loop-disabled", "false",
+ "vserver.l-interface.network-name", "vUSP_DPA3_OAM_3750",
+ "vserver.l-interface.l3-interface-ipv4-address-list.l3-inteface-ipv4-address", //continues
+ "135.144.3.50",
+ "vserver.vserver-id", "78fe4342-8f85-49ba-be9f-d0c1bdf1ba7b",
+ "generic-vnf.service-id", "e433710f-9217-458d-a79d-1c7aff376d89",
+ "complex.city", "AAIDefault",
+ "vserver.in-maint", "N",
+ "complex.state", "NJ",
+ "vserver.vserver-name", "ctsf0002vm025",
+ "complex.physical-location-id", "LSLEILAA",
+ "tenant.tenant-id", "d0719b845a804b368f8ac0bba39e188b",
+ "vserver.prov-status", "PROV",
+ "generic-vnf.vnf-name", "ctsf0002v",
+ "vserver.l-interface.interface-name", // continues
+ "ctsf0002v-ALU-LCP-Pair07-oziwyxlxwdyc-1-a4psuz5awjw7-ALU-LCP-ETH2-ygmny7m7rpb5",
+ "generic-vnf.vnf-type", "vUSP-vCTS",
+ "cloud-region.identity-url", "https://auth.pdk11.cci.att.com:5000/v2.0"
+ ),
+ "closedLoopAlarmStart", "1507143409107000",
+ "closedLoopEventStatus", "ONSET",
+ "closedLoopControlName", closedLoopControlName,
+ "version", "1.0.2",
+ "target", "vserver.vserver-name",
+ "resourceInstance", json("resourceName", "",
+ "resourceInstanceName", ""
+ ),
+ "requestID", requestId,
+ "from", "DCAE",
+ "serviceInstance", json("serviceInstanceName", "",
+ "serviceName", ""
+ )
+ );
+ }
+ }
+}
diff --git a/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimDmaap.java b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimDmaap.java
new file mode 100644
index 000000000..3a80f9581
--- /dev/null
+++ b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimDmaap.java
@@ -0,0 +1,266 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/test
+ * ================================================================================
+ * 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.test;
+
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class simulates a UEB/DMAAP server.
+ */
+
+@Path("/")
+public class SimDmaap {
+ private static Logger logger = LoggerFactory.getLogger(SimDmaap.class);
+
+ // maps topic name to 'Topic' instance
+ static Map<String,Topic> topicTable = new ConcurrentHashMap<>();
+
+ /**
+ * Each instance of this class corresponds to a DMAAP or UEB topic.
+ */
+ static class Topic {
+ // topic name
+ String topic;
+
+ // maps group name into group instance
+ Map<String,Group> groupTable = new ConcurrentHashMap<>();
+
+ /**
+ * Create or get a Topic.
+ *
+ * @param name the topic name
+ * @return the associated Topic instance
+ */
+ static Topic createOrGet(String name) {
+ // look up the topic name
+ Topic topicObj = topicTable.get(name);
+ if (topicObj == null) {
+ // no entry found -- the following will create one, without
+ // the need for explicit synchronization
+ topicTable.putIfAbsent(name, new Topic(name));
+ topicObj = topicTable.get(name);
+ }
+ return topicObj;
+ }
+
+ /**
+ * Constructor - initialize the 'topic' field.
+ *
+ * @param topic the topic name
+ */
+ private Topic(String topic) {
+ this.topic = topic;
+ }
+
+ /**
+ * Handle an incoming '/events/{topic}' POST REST message.
+ *
+ * @param the body of the REST message
+ * @return the appropriate JSON response
+ */
+ String post(String data) {
+ // start of message processing
+ long startTime = System.currentTimeMillis();
+
+ // current and ending indices to the 'data' field
+ int cur = 0;
+ int end = data.length();
+
+ // the number of messages retrieved so far
+ int messageCount = 0;
+
+ while (cur < end) {
+ // The body of the message may consist of multiple JSON messages,
+ // each preceded by 3 integers separated by '.'. The second one
+ // is the length, in bytes (the third seems to be some kind of
+ // channel identifier).
+
+ int leftBrace = data.indexOf('{', cur);
+ if (leftBrace < 0) {
+ // no more messages
+ break;
+ }
+ String[] prefix = data.substring(cur,leftBrace).split("\\.");
+ if (prefix.length == 3) {
+ try {
+ // determine length of message, and advance current position
+ int length = Integer.parseInt(prefix[1]);
+ cur = leftBrace + length;
+
+ // extract message, and update count -- each '\' is converted
+ // to '\\', and each double quote has a '\' character placed
+ // before it, so the overall message can be placed in double
+ // quotes, and parsed as a literal string
+ String message = data.substring(leftBrace, cur)
+ .replace("\\", "\\\\").replace("\"", "\\\"")
+ .replace("\n", "\\n");
+ messageCount += 1;
+
+ // send to all listening groups
+ for (Group group : groupTable.values()) {
+ group.messages.add(message);
+ }
+ } catch (Exception e) {
+ logger.info("{}: {}", prefix[1], e);
+ break;
+ }
+ } else if (cur == 0) {
+ // there is only a single message -- extract it, and update count
+ String message = data.substring(leftBrace, end)
+ .replace("\\", "\\\\").replace("\"", "\\\"")
+ .replace("\n", "\\n");
+ messageCount += 1;
+
+ // send to all listening grops
+ for (Group group : groupTable.values()) {
+ group.messages.add(message);
+ }
+ break;
+ } else {
+ // don't know what this is -- toss it
+ break;
+ }
+ }
+
+ // generate response message
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ return "{\n"
+ + " \"count\": " + messageCount + ",\n"
+ + " \"serverTimeMs\": " + elapsedTime + "\n"
+ + "}";
+ }
+
+ /**
+ * read one or more incoming messages.
+ *
+ * @param group the 'consumerGroup' value
+ * @param timeout how long to wait for a message, in milliseconds
+ * @param limit the maximum number of messages to receive
+ * @return a JSON array, containing 0-limit messages
+ */
+ String get(String group, long timeout, int limit)
+ throws InterruptedException {
+ // look up the group -- create one if it doesn't exist
+ Group groupObj = groupTable.get(group);
+ if (groupObj == null) {
+ // no entry found -- the following will create one, without
+ // the need for explicit synchronization
+ groupTable.putIfAbsent(group, new Group());
+ groupObj = groupTable.get(group);
+ }
+
+ // pass it on to the 'Group' instance
+ return groupObj.get(timeout, limit);
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Each instance of this class corresponds to a Consumer Group.
+ */
+ static class Group {
+ // messages queued for this group
+ private BlockingQueue<String> messages = new LinkedBlockingQueue<>();
+
+ /**
+ * Retrieve messages sent to this group.
+ *
+ * @param timeout how long to wait for a message, in milliseconds
+ * @param limit the maximum number of messages to receive
+ * @return a JSON array, containing 0-limit messages
+ */
+ String get(long timeout, int limit) throws InterruptedException {
+ String message = messages.poll(timeout, TimeUnit.MILLISECONDS);
+ if (message == null) {
+ // timed out without messages
+ return "[]";
+ }
+
+ // use 'StringBuilder' to assemble the response -- add the first message
+ StringBuilder builder = new StringBuilder();
+ builder.append("[\"").append(message);
+
+ // add up to '<limit>-1' more messages
+ for (int i = 1 ; i < limit ; i += 1) {
+ // fetch the next message -- don't wait if it isn't currently there
+ message = messages.poll();
+ if (message == null) {
+ // no more currently available
+ break;
+ }
+ builder.append("\",\"").append(message);
+ }
+ builder.append("\"]");
+ return builder.toString();
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * Process an HTTP POST to /events/{topic}.
+ */
+ @POST
+ @Path("/events/{topic}")
+ @Consumes("application/cambria")
+ @Produces(MediaType.APPLICATION_JSON)
+ public String send(@PathParam("topic") String topic,
+ String data) {
+ logger.info("Send: topic={}", topic);
+ return Topic.createOrGet(topic).post(data);
+ }
+
+ /**
+ * Process an HTTP GET to /events/{topic}/{group}/{id}.
+ */
+ @GET
+ @Path("/events/{topic}/{group}/{id}")
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String receive(@PathParam("topic") String topic,
+ @PathParam("group") String group,
+ @PathParam("id") String id,
+ @QueryParam("timeout") long timeout,
+ @QueryParam("limit") int limit)
+ throws InterruptedException {
+
+ logger.info("Receive: topic={}, group={}, id={}, timeout={}, limit={}",
+ topic, group, id, timeout, limit);
+ return Topic.createOrGet(topic).get(group, timeout, limit);
+ }
+}
diff --git a/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimGuard.java b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimGuard.java
new file mode 100644
index 000000000..578dd6dc3
--- /dev/null
+++ b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimGuard.java
@@ -0,0 +1,65 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/test
+ * ================================================================================
+ * 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.test;
+
+import com.google.gson.JsonObject;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * minimal Guard Simulator -- just enough to support the current tests.
+ */
+@Path("/")
+public class SimGuard {
+ private static Logger logger = LoggerFactory.getLogger(SimGuard.class);
+
+ // used for JSON <-> String conversion
+ private static StandardCoder coder = new StandardCoder();
+
+ /**
+ * Process an HTTP POST to /pdp/.
+ */
+ @POST
+ @Path("/pdp/")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public String query(String data) throws CoderException {
+
+ JsonObject msg = coder.decode(data, JsonObject.class);
+ logger.info("SimGuard query:\n{}", Util.prettyPrint(msg));
+
+ JsonObject response = new JsonObject();
+ response.addProperty("status", "Permit");
+ logger.info("Returning:\n{}", Util.prettyPrint(response));
+
+ return response.toString();
+ }
+}
diff --git a/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/Util.java b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/Util.java
new file mode 100644
index 000000000..393a030f0
--- /dev/null
+++ b/controlloop/m2/test/src/test/java/org/onap/policy/m2/test/Util.java
@@ -0,0 +1,502 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/test
+ * ================================================================================
+ * 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.test;
+
+import static org.junit.Assert.assertTrue;
+
+import com.att.nsa.cambria.client.CambriaBatchingPublisher;
+import com.att.nsa.cambria.client.CambriaClientBuilders;
+import com.att.nsa.cambria.client.CambriaClientBuilders.ConsumerBuilder;
+import com.att.nsa.cambria.client.CambriaClientBuilders.PublisherBuilder;
+import com.att.nsa.cambria.client.CambriaConsumer;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Util {
+ private static Logger logger = LoggerFactory.getLogger(Util.class);
+
+ // used for JSON <-> String conversion
+ private static StandardCoder coder = new StandardCoder();
+
+ // used for pretty-printing: gson.toJson(JsonObject obj)
+ private static Gson gson =
+ new GsonBuilder().setPrettyPrinting().serializeNulls().create();
+
+ // contains the currently running set of servers
+ private static List<Server> runningServers = new LinkedList<>();
+
+ /**
+ * Read from an 'InputStream' until EOF or until it is closed. This method
+ * may block, depending on the type of 'InputStream'.
+ *
+ * @param input This is the input stream
+ * @return A 'String' containing the contents of the input stream
+ */
+ public static String inputStreamToString(InputStream input) {
+ StringBuilder sb = new StringBuilder();
+ byte[] buffer = new byte[8192];
+ int length;
+
+ try {
+ while ((length = input.read(buffer)) > 0) {
+ sb.append(new String(buffer, 0, length));
+ }
+ } catch (IOException e) {
+ // return what we have so far
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Read in a file, converting the contents to a string.
+ *
+ * @param file the input file
+ * @return a String containing the contents of the file
+ */
+ public static String fileToString(File file)
+ throws IOException, FileNotFoundException {
+ try (FileInputStream fis = new FileInputStream(file)) {
+ String string = inputStreamToString(fis);
+ return string;
+ }
+ }
+
+ /**
+ * Create a file containing the contents of the specified string.
+ *
+ * @param string the input string
+ * @param suffix the suffix to pass to 'createTempFile
+ * @return a File, whose contents contain the string
+ */
+ public static File stringToFile(String string, String suffix)
+ throws IOException {
+ File file = File.createTempFile("templates-util", suffix);
+ file.deleteOnExit();
+
+ try (FileOutputStream fos = new FileOutputStream(file)) {
+ fos.write(string.getBytes());
+ }
+ return file;
+ }
+
+ /**
+ * Create a file containing the contents of the specified string.
+ *
+ * @param string the input string
+ * @return a File, whose contents contain the string
+ */
+ public static File stringToFile(String string)
+ throws IOException {
+ return stringToFile(string, "");
+ }
+
+ /**
+ * This method converts a YAML string into one that can be embedded into
+ * a '.drl' file.
+ *
+ * @param yaml the input string, which is typically read from a file
+ * @return the converted string
+ */
+ public static String convertYaml(String yaml) {
+ yaml = yaml.replace("\n", "%0A");
+ yaml = yaml.replace("\r", "");
+ yaml = yaml.replace(":", "%3A");
+ yaml = yaml.replace(' ', '+');
+ return yaml;
+ }
+
+ /**
+ * This is a convenience method which reads a file into a string, and
+ * then does a set of string replacements on it. The real purpose is to
+ * make it easy to do '${parameter}' replacements in files, as part of
+ * building a Drools artifact.
+ *
+ * @param fileName this is the input file name
+ * @param args these parameters come in pairs:
+ * 'input-string' and 'output-string'.
+ * @return a String containing the contents of the file, with the parameters
+ * replaced
+ */
+ public static String openAndReplace(String fileName, String... args)
+ throws IOException, FileNotFoundException {
+ String text = fileToString(new File(fileName));
+ for (int i = 0 ; i < args.length ; i += 2) {
+ text = text.replace(args[i], args[i + 1]);
+ }
+ return text;
+ }
+
+ /**
+ * Convert an Object to a JsonElement.
+ *
+ * @param object the object to convert
+ * @return a JsonElement that corresponds to 'object'
+ */
+ public static JsonElement toJsonElement(Object object) {
+ if (object == null || object instanceof JsonElement) {
+ return (JsonElement) object;
+ }
+ if (object instanceof Number) {
+ return new JsonPrimitive((Number) object);
+ }
+ if (object instanceof Boolean) {
+ return new JsonPrimitive((Boolean) object);
+ }
+ if (object instanceof Character) {
+ return new JsonPrimitive((Character) object);
+ }
+ return new JsonPrimitive(object.toString());
+ }
+
+ /**
+ * This is a convenience method to build a 'JsonObject', and populate
+ * it with a set of keyword/value pairs.
+ *
+ * @param data this parameter comes in pairs: 'keyword', and 'value'
+ * @return the populated JsonObject
+ */
+ public static JsonObject json(Object... data) {
+ JsonObject obj = new JsonObject();
+ for (int i = 0 ; i < data.length ; i += 2) {
+ obj.add(data[i].toString(), toJsonElement(data[i + 1]));
+ }
+ return obj;
+ }
+
+ /**
+ * Convert a JsonElement to a String (pretty-printing).
+ *
+ * @param jsonElement the object to convert
+ * @return a pretty-printed string
+ */
+ public static String prettyPrint(JsonElement jsonElement) {
+ return gson.toJson(jsonElement);
+ }
+
+ /**
+ * This method is used to check whether a JSON message has a set of fields
+ * populated with the values expected.
+ *
+ * @param subset this is a 'JsonObject', which contains field names and
+ * values (the values are interpreted as regular expressions). The values
+ * may also be 'JsonObject' instances, in which case they are compared
+ * recursively.
+ * @param whole ordinarily, this will be a 'JsonObject', and will contain
+ * a superset of the fields in 'subset'. If not, the 'assert' fails.
+ */
+ public static void assertSubset(JsonObject subset, Object whole) {
+ StringBuilder sb = new StringBuilder();
+ assertSubsetAssist(sb, "", subset, toJsonElement(whole));
+ String sbString = sb.toString();
+ assertTrue(sbString, sbString.isEmpty());
+ }
+
+ /**
+ * This is similar to 'assertSubset', but just returns 'true' if the
+ * pattern matches.
+ *
+ * @param subset this is a 'JsonObject', which contains field names and
+ * values (the values are interpreted as regular expressions). The values
+ * may also be 'JsonObject' instances, in which case they are compared
+ * recursively.
+ * @param whole ordinarily, this will be a 'JsonObject', and will contain
+ * a superset of the fields in 'subset'. If not, the 'assert' fails.
+ * @return 'true' if 'whole' is a superset of 'subset'
+ */
+ public static boolean testSubset(JsonObject subset, Object whole) {
+ StringBuilder sb = new StringBuilder();
+ assertSubsetAssist(sb, "", subset, toJsonElement(whole));
+ return sb.length() == 0;
+ }
+
+ /**
+ * This is an internal support method for 'assertSubset' and 'testSubset',
+ * and handles the recursive comparison.
+ *
+ * @param sb a 'StringBuilder', which is appended to when there are
+ * mismatches
+ * @param prefix the field name being compared (the empty string indicates
+ * the top-level field).
+ * @param subset the 'JsonObject' being compared at this level
+ * @param argWhole the value being tested -- if it is not a 'JsonObject',
+ * the comparison fails
+ */
+ private static void assertSubsetAssist(StringBuilder sb, String prefix, JsonObject subset, JsonElement argWhole) {
+ if (!(argWhole.isJsonObject())) {
+ sb.append(prefix).append(" is not a JsonObject\n");
+ return;
+ }
+ JsonObject whole = argWhole.getAsJsonObject();
+ for (String key : subset.keySet()) {
+ String fullKey = (prefix.isEmpty() ? key : prefix + "." + key);
+ JsonElement value = subset.get(key);
+ JsonElement value2 = whole.get(key);
+ if (value.isJsonObject()) {
+ assertSubsetAssist(sb, fullKey, value.getAsJsonObject(), value2);
+ } else if (!value.equals(value2)
+ && (value2 == null || !value2.toString().matches(value.toString()))) {
+ sb.append(fullKey)
+ .append(": got ")
+ .append(String.valueOf(value2))
+ .append(", expected ")
+ .append(String.valueOf(value))
+ .append("\n");
+ }
+ }
+ }
+
+ /**
+ * Do whatever needs to be done to start the server. I don't know exactly
+ * what abstractions the various pieces provide, but the following code
+ * ties the pieces together, and starts up the server.
+ *
+ * @param name used as the 'ServerConnector' name, and also used to generate
+ * a name for the server thread
+ * @param host the host IP address to bind to
+ * @param port the port to bind to
+ * @param clazz the class containing the provider methods
+ */
+ public static void startRestServer(String name, String host, int port, Class<?> clazz) {
+ ServletContextHandler context =
+ new ServletContextHandler(ServletContextHandler.SESSIONS);
+ context.setContextPath("/");
+
+ final Server jettyServer = new Server();
+
+ ServerConnector connector = new ServerConnector(jettyServer);
+ connector.setName(name);
+ connector.setReuseAddress(true);
+ connector.setPort(port);
+ connector.setHost(host);
+
+ jettyServer.addConnector(connector);
+ jettyServer.setHandler(context);
+
+ ServletHolder holder =
+ context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class.getName(), "/*");
+ holder.setInitParameter(
+ "jersey.config.server.provider.classnames",
+ "org.onap.policy.common.gson.GsonMessageBodyHandler"
+ + "," + clazz.getName());
+
+ synchronized (runningServers) {
+ runningServers.add(jettyServer);
+ }
+
+ new Thread(() -> {
+ try {
+ jettyServer.start();
+ jettyServer.join();
+ logger.info("{}: back from jettyServer.join()", name);
+ } catch (Exception e) {
+ logger.info(name + ": Exception starting jettyServer", e);
+ }
+ }, "REST Server: " + name).start();
+ }
+
+ private static boolean initNeeded = true;
+
+ /**
+ * This method starts services shared by all of the tests. The services are
+ * started the first time it is invoked -- subsequent invocations have no
+ * effect.
+ */
+ public static void commonInit() {
+ if (initNeeded) {
+ initNeeded = false;
+
+ // start DMAAP Simulator
+ startRestServer("simdmaap", "127.0.71.250", 3904, SimDmaap.class);
+
+ // start Guard Simulator
+ startRestServer("simguard", "127.0.71.201", 8443, SimGuard.class);
+
+ // start PolicyEngine
+ PolicyEngineConstants.getManager().configure(new Properties());
+ PolicyEngineConstants.getManager().start();
+ }
+ }
+
+ /**
+ * This method shuts down all of the servers that were started.
+ */
+ public static void commonShutdown() {
+ synchronized (runningServers) {
+ for (Server server : runningServers) {
+ try {
+ server.stop();
+ } catch (Exception e) {
+ logger.info("Exception shutting down server: {}", e);
+ }
+ }
+ runningServers.clear();
+ initNeeded = true;
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * This class is used to create an outgoing (publisher) topic message
+ * channel. 'topic' is the only parameter -- everything else is hard-wired.
+ */
+ public static class Output {
+ CambriaBatchingPublisher publisher;
+ String topic;
+
+ /**
+ * Constructor - create the outgoing topic message channel.
+ *
+ * @param topic a DMAAP or UEB topic name
+ */
+ public Output(String topic) throws Exception {
+ this.topic = topic;
+ PublisherBuilder builder =
+ new CambriaClientBuilders.PublisherBuilder();
+ builder
+ .usingHosts("127.0.71.250")
+ .onTopic(topic)
+ .withSocketTimeout(5000);
+ publisher = builder.build();
+ }
+
+ /**
+ * Send a JSON message out this channel.
+ *
+ * @param msg a 'JsonObject' containing the message to be sent
+ */
+ public void send(JsonObject msg) throws Exception {
+ logger.info("Sending message, topic = {}\n{}",
+ topic, gson.toJson(msg));
+ publisher.send("123", msg.toString());
+ }
+
+ /**
+ * Close the channel.
+ */
+ public void close() {
+ publisher.close();
+ }
+ }
+
+ /* ============================================================ */
+
+ /**
+ * This class is used to create an incoming (consumer) topic message channel,
+ * as well as a Thread that reads from it. Incoming messages are placed in
+ * a 'LinkedBlockingQueue', which may be polled for messages.
+ */
+ public static class Input extends Thread {
+ CambriaConsumer consumer;
+ String topic;
+ LinkedBlockingQueue<JsonObject> queue = new LinkedBlockingQueue<>();
+ volatile boolean running = true;
+
+ /**
+ * Constructor - create the incoming topic message channel.
+ *
+ * @param topic a DMAAP or UEB topic name
+ */
+ public Input(String topic) throws Exception {
+ this.topic = topic;
+
+ // initialize reader
+ ConsumerBuilder builder = new CambriaClientBuilders.ConsumerBuilder();
+ builder
+ .knownAs(UUID.randomUUID().toString(), "1")
+ .usingHosts("127.0.71.250")
+ .onTopic(topic)
+ .waitAtServer(15000)
+ .receivingAtMost(100)
+ .withSocketTimeout(20000);
+ consumer = builder.build();
+ start();
+ }
+
+ /**
+ * This is the Thread main loop. It fetches messages, and queues them.
+ */
+ @Override
+ public void run() {
+ while (running) {
+ try {
+ for (String message : consumer.fetch()) {
+ // a message was received -- parse it as JSON
+ JsonObject msg = coder.decode(message, JsonObject.class);
+
+ // construct a message to print, and print it
+ logger.info("Received message, topic = {}\n{}",
+ topic, gson.toJson(msg));
+
+ // queue the message
+ queue.add(msg);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * Return the first message in the queue. If none are available, wait up
+ * to 30 seconds for one to appear.
+ *
+ * @return a 'JsonObject' if a message has been received, and 'null' if not
+ */
+ public JsonObject poll() throws InterruptedException {
+ return queue.poll(30, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Stop the thread, and close the channel.
+ */
+ public void close() {
+ running = false;
+ consumer.close();
+ }
+ }
+}
diff --git a/controlloop/m2/test/src/test/resources/appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml b/controlloop/m2/test/src/test/resources/appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml
new file mode 100644
index 000000000..ee25b03bb
--- /dev/null
+++ b/controlloop/m2/test/src/test/resources/appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml
@@ -0,0 +1,83 @@
+controlLoop:
+ version: 2.0.0
+ controlLoopName: ControlLoop-vUSP-vCTS-cbed919f-2212-4ef7-8051-fe6308da1bda
+ services:
+ - serviceName: vUSP
+ resources:
+ - resourceName: vCTS
+ resourceType: VF
+ - resourceName: vCOM
+ resourceType: VF
+ - resourceName: vRAR
+ resourceType: VF
+ - resourceName: vLCS
+ resourceType: VF
+ - resourceName: v3CB
+ resourceType: VF
+ trigger_policy: unique-policy-id-1-restart
+ timeout: 60
+
+policies:
+ - id: unique-policy-id-1-restart
+ name: Restart Policy
+ description:
+ actor: APPCLCM
+ recipe: Restart
+ target:
+ type: VM
+ retry: 1
+ timeout: 35
+ success: final_success
+ failure: unique-policy-id-2-rebuild
+ failure_timeout: unique-policy-id-2-rebuild
+ failure_retries: unique-policy-id-2-rebuild
+ failure_guard: unique-policy-id-2-rebuild
+ failure_exception: final_failure_exception
+
+ - id: unique-policy-id-2-rebuild
+ name: Rebuild Policy
+ description:
+ actor: APPCLCM
+ recipe: Rebuild
+ target:
+ type: VM
+ retry: 0
+ timeout: 35
+ success: final_success
+ failure: unique-policy-id-3-migrate
+ failure_timeout: unique-policy-id-3-migrate
+ failure_retries: unique-policy-id-3-migrate
+ failure_guard: unique-policy-id-3-migrate
+ failure_exception: final_failure_exception
+
+ - id: unique-policy-id-3-migrate
+ name: Migrate Policy
+ description:
+ actor: APPCLCM
+ recipe: Migrate
+ target:
+ type: VM
+ retry: 0
+ timeout: 35
+ success: final_success
+ failure: unique-policy-id-4-evacuate
+ failure_timeout: unique-policy-id-4-evacuate
+ failure_retries: unique-policy-id-4-evacuate
+ failure_guard: unique-policy-id-4-evacuate
+ failure_exception: final_failure_exception
+
+ - id: unique-policy-id-4-evacuate
+ name: Evacuate Policy
+ description:
+ actor: APPCLCM
+ recipe: Evacuate
+ target:
+ type: VM
+ retry: 0
+ timeout: 35
+ success: final_success
+ failure: final_failure
+ failure_timeout: final_failure_timeout
+ failure_retries: final_failure_retries
+ failure_guard: final_failure_guard
+ failure_exception: final_failure_exception
diff --git a/controlloop/m2/test/src/test/resources/appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl b/controlloop/m2/test/src/test/resources/appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl
new file mode 100644
index 000000000..2a24f1312
--- /dev/null
+++ b/controlloop/m2/test/src/test/resources/appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl
@@ -0,0 +1,649 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/test
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.p_${unique};
+
+import org.drools.core.spi.KnowledgeHelper;
+
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopEventStatus;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopNotification;
+import org.onap.policy.controlloop.ControlLoopNotificationType;
+import org.onap.policy.controlloop.VirtualControlLoopNotification;
+import org.onap.policy.controlloop.compiler.ControlLoopCompiler;
+import org.onap.policy.controlloop.policy.ControlLoop;
+import org.onap.policy.controlloop.policy.ControlLoopPolicy;
+import org.onap.policy.drools.core.PolicySession;
+import org.onap.policy.drools.droolsinit.DroolsInitFeature;
+import org.onap.policy.drools.system.PolicyController;
+import org.onap.policy.drools.system.PolicyEngineConstants;
+import org.onap.policy.guard.GuardContext;
+import org.onap.policy.guard.PolicyGuardResponse;
+
+import org.onap.policy.m2.base.GuardAdjunct;
+import org.onap.policy.m2.base.Transaction;
+import org.onap.policy.m2.base.Util;
+import org.onap.policy.m2.appclcm.AppcLcmOperation;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayInputStream;
+import java.net.URLDecoder;
+import java.time.Instant;
+import java.util.LinkedList;
+import java.util.Properties;
+
+declare Params
+ closedLoopControlName : String
+ controlLoopYaml : String
+ notificationTopic : String
+ operationTopic : String
+end
+
+declare Context
+ logger : Logger
+ metricsLogger : Logger
+ auditLogger : Logger
+ policy : ControlLoopPolicy
+ guardContext : GuardContext
+ maxObjectCount : long
+ closedLoopControlName : String
+ controlLoopYaml : String
+ notificationTopic : String
+ operationTopic : String
+end
+
+// this object is to provide support for timeouts
+// due to a bug in drools' built in timers
+declare ControlLoopTimer
+ closedLoopControlName : String
+ requestId : String
+ delay : String
+ expired : boolean
+ //timerType is the type of timer: either "ClosedLoop" or "Operation"
+ timerType : String
+end
+
+function void sendNotification(Context context,
+ KnowledgeHelper drools,
+ ControlLoopNotification notification)
+{
+ if (notification != null)
+ {
+ notification.setFrom("policy");
+ notification.setPolicyName(drools.getRule().getName());
+ notification.setPolicyScope("${policyScope}");
+ notification.setPolicyVersion("${policyVersion}");
+
+ PolicyEngineConstants.getManager().deliver(context.getNotificationTopic(), notification);
+ }
+}
+
+function Context setParams(Context context)
+{
+ context.setClosedLoopControlName("${closedLoopControlName}");
+ context.setControlLoopYaml("${controlLoopYaml}");
+ context.setNotificationTopic("${notificationTopic}");
+ context.setOperationTopic("${operationTopic}");
+ context.setPolicy(ControlLoopCompiler.compile
+ (new ByteArrayInputStream
+ (URLDecoder.decode(context.getControlLoopYaml(),
+ "UTF-8").getBytes()), null));
+ return context;
+}
+
+rule "${policyName}.INIT"
+ salience 100
+ when
+ $init : DroolsInitFeature.Init()
+ not(Context(closedLoopControlName == "${closedLoopControlName}"))
+ then
+ {
+ Logger logger = LoggerFactory.getLogger("${policyName}.drl");
+ Logger metricsLogger = LoggerFactory.getLogger("com.att.eelf.metrics");
+ Logger auditLogger = LoggerFactory.getLogger("com.att.eelf.audit");
+ logger.info("This is ${policyName}.INIT");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+
+ Context context = new Context();
+ context.setLogger(logger);
+ // add metricsLogger to context
+ context.setMetricsLogger(metricsLogger);
+ context.setAuditLogger(auditLogger);
+ context = setParams(context);
+
+ PolicySession session = PolicySession.getCurrentSession();
+
+ context.setGuardContext(new GuardContext(session));
+
+ try
+ {
+ // initially, set a default maximum object count of 1000
+ context.setMaxObjectCount(1000);
+
+ // 'IllegalArgumentException' if the properties can't be found
+ PolicyController policyController =
+ Util.getPolicyController(session);
+ Properties properties = policyController.getProperties();
+
+ String maxObjectCount =
+ properties.getProperty("overload.maxObjectCount","").trim();
+ if (!maxObjectCount.isEmpty())
+ {
+ // A value has been specified in the properties file --
+ // 'NumberFormatException' if the value is bad
+ context.setMaxObjectCount(Long.valueOf(maxObjectCount));
+ }
+ }
+ catch (IllegalArgumentException e)
+ {
+ logger.error("${policyName}.INIT: Can't locate properties", e);
+ }
+ catch (Exception e)
+ {
+ logger.error
+ ("${policyName}.INIT: Can't decode 'overload.maxObjectCount'", e);
+ }
+
+ insert(context);
+ }
+end
+
+/*
+ * This rule fires when a drools update occurs. Its purpose is to
+ * update the expandable parameters of the context object to reflect
+ * the new state of the policy.
+ */
+rule "${policyName}.REINIT"
+ salience 100
+ when
+ $init : DroolsInitFeature.Init()
+ $context : Context(closedLoopControlName == "${closedLoopControlName}"
+ && (controlLoopYaml != "${controlLoopYaml}"
+ || notificationTopic != "${notificationTopic}" || operationTopic != "${operationTopic}"))
+ then
+ {
+ Logger logger = $context.getLogger();
+ // get metricsLogger from context
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.REINIT");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+
+ $context = setParams($context);
+
+ modify($context)
+ {
+ }
+ }
+end
+
+/*
+ * Fires when an incoming 'ONSET' event occurs, without an associated
+ * 'Transaction' instance
+ */
+rule "${policyName}.EVENT.NO-TRANSACTION"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $event : VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ONSET)
+ not(Transaction(closedLoopControlName == $context.closedLoopControlName, requestId == $event.requestId))
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ logger.info("This is ${policyName}.EVENT.NO-TRANSACTION");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+
+ // check for overload
+ if (drools.getWorkingMemory().getFactCount() >
+ $context.getMaxObjectCount())
+ {
+ // send 'Overload' notification
+ ControlLoopNotification notification =
+ new VirtualControlLoopNotification($event);
+ notification.setNotification(ControlLoopNotificationType.REJECTED);
+ notification.setMessage("Overload in progress");
+ sendNotification($context, drools, notification);
+
+ // discard event, and return
+ auditLogger.info(Instant.now() + "Event End: FAIL: Overload in progress");
+ retract($event);
+ return;
+ }
+
+ Transaction transaction =
+ new Transaction(drools.getWorkingMemory(),
+ $context.getClosedLoopControlName(),
+ $event.getRequestId(),
+ $context.getPolicy());
+
+ //
+ // Setup the Overall Control Loop timer
+ //
+ ControlLoopTimer clTimer =
+ new ControlLoopTimer($event.getClosedLoopControlName(), $event.getRequestId().toString(),
+ transaction.getTimeout(), false, "ClosedLoop");
+
+ insert(clTimer);
+
+ // check that the event and a&ai is valid
+ if (!transaction.isControlLoopEventValid($event) ||
+ !AppcLcmOperation.isAaiValid(transaction, $event)) {
+ ControlLoopNotification notification =
+ new VirtualControlLoopNotification($event);
+ notification.setNotification(ControlLoopNotificationType.REJECTED);
+ notification.setMessage(transaction.getNotificationMessage());
+ sendNotification($context, drools, notification);
+
+ // discard event, and return
+ auditLogger.info(Instant.now() + "Event End: FAIL: Invalid event/AAI");
+ retract($event);
+ return;
+ }
+
+ // this adjunct needs to be in place before the first 'Operation'
+ // is created
+ GuardAdjunct.create(transaction, $context.getGuardContext());
+ insert(transaction);
+
+ // this creates the initial 'Operation'
+ transaction.setControlLoopEvent($event);
+ retract($event);
+
+ // send out an active notification
+ ControlLoopNotification notification = transaction.getNotification(null);
+ notification.setNotification(ControlLoopNotificationType.ACTIVE);
+ sendNotification($context, drools, notification);
+ }
+end
+
+/*
+ * Fires when 'ONSET' and 'ABATED' events have occured before an appc request was processed
+ */
+rule "${policyName}.TRANSACTION.ABATED.NO-REQUEST"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state != AppcLcmOperation.LCM_PENDING &&
+ state != "COMPLETE" && state != AppcLcmOperation.LCM_COMPLETE)
+ $event : VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ABATED, requestId == $transaction.requestId)
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("${policyName}.TRANSACTION.ABATED.NO-REQUEST");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+
+ $transaction.incomingMessage($event);
+ modify($transaction)
+ {
+ }
+ }
+end
+
+rule "${policyName}.TRANSACTION.LCM.GUARD_PENDING.RESPONSE"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_GUARD_PENDING)
+ $response : PolicyGuardResponse(requestId == $transaction.getRequestId())
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.TRANSACTION.LCM.GUARD_PENDING.RESPONSE");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ sendNotification($context, drools,
+ $transaction.incomingMessage($response));
+ //
+ // insert operation timeout object
+ //
+ ControlLoopTimer opTimer =
+ new ControlLoopTimer($transaction.getClosedLoopControlName(), $transaction.getRequestId().toString(),
+ $transaction.getOperationTimeout(), false, "Operation");
+ insert(opTimer);
+
+ modify($transaction)
+ {
+ }
+ retract($response);
+ }
+end
+
+/*
+ * Initial state for LCM operations (Restart/Rebuild/Migrate)
+ */
+rule "${policyName}.TRANSACTION.LCM.BEGIN"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction : Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_BEGIN)
+ ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), !expired, timerType == "Operation")
+ not(VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ABATED, requestId == $transaction.requestId))
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.TRANSACTION.LCM.BEGIN");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ Object request = null;
+ try {
+ request = $transaction.getCurrentOperation().getRequest();
+ } catch (ControlLoopException e) {
+ logger.error("request could not be formed due to: "+e.getMessage());
+ return;
+ }
+ PolicyEngineConstants.getManager().deliver($context.getOperationTopic(), request);
+
+ // send notification
+ sendNotification($context, drools,
+ $transaction.initialOperationNotification());
+
+ modify($transaction)
+ {
+ }
+ }
+end
+
+/*
+ * We are waiting for an LCM response, and one has occurred
+ * (it may or may not be the one we were expecting)
+ */
+rule "${policyName}.TRANSACTION.LCM.PENDING.RESPONSE"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_PENDING)
+ $opTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), !expired, timerType == "Operation")
+ $response : AppcLcmDmaapWrapper(body.output.commonHeader.requestId == $transaction.getRequestId())
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.TRANSACTION.LCM.PENDING.RESPONSE");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ ControlLoopNotification notification = $transaction.incomingMessage($response);
+ sendNotification($context, drools, notification);
+ if (notification != null) {
+ retract($opTimer);
+ }
+ modify($transaction)
+ {
+ }
+ retract($response);
+ }
+end
+
+/*
+*
+* This is the timer that manages the timeout for an individual operation.
+* Due to a bug in the drools code, the drools timer needed to be split from most of the objects in the when clause
+*
+*/
+rule "${policyName}.LCM.OPERATION.TIMER.FIRED"
+ timer (expr: $timeout)
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $opTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, $timeout : delay, !expired, timerType == "Operation")
+ then
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ modify($opTimer){setExpired(true)};
+ end
+
+/*
+ * We are waiting for an LCM response, but the timer expired before
+ * receiving one.
+ */
+rule "${policyName}.TRANSACTION.LCM.PENDING.TIMEOUT"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_PENDING, $timeout : getOperationTimeout())
+ $opTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), expired, timerType == "Operation")
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.TRANSACTION.LCM.PENDING.TIMEOUT: "
+ + $transaction.getOperationTimeout());
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ sendNotification($context, drools, $transaction.timeout());
+ retract($opTimer);
+ modify($transaction)
+ {
+ }
+ }
+end
+
+rule "${policyName}.TRANSACTION.LCM.PENDING.ERROR"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == AppcLcmOperation.LCM_ERROR)
+ $timers : LinkedList()
+ from collect (ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "Operation"))
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.TRANSACTION.LCM.ERROR");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ $transaction.processError();
+ for (Object timer : $timers) {
+ retract(timer);
+ }
+ modify($transaction)
+ {
+ }
+ }
+end
+
+/*
+ * We are in the 'LCM_COMPLETE' state, meaning no operations are in progress,
+ * and no 'ABATED' message is expected so the transaction can complete.
+ */
+rule "${policyName}.TRANSACTION.COMPLETE.NOT-ABATED"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}", getPolicy().getControlLoop().getAbatement() == false)
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == "COMPLETE")
+ $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "ClosedLoop")
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ logger.info("This is ${policyName}.TRANSACTION.COMPLETE.NOT-ABATED");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ auditLogger.info(Instant.now() + " Event End: SUCCESS: NOT-ABATED");
+ retract($transaction);
+ retract($clTimer);
+ $transaction.cleanup();
+
+ sendNotification($context, drools, $transaction.finalNotification());
+ }
+end
+
+/*
+ * We are in the 'COMPLETE' state, meaning no operations are in progress,
+ * and we have received an 'ABATED' message.
+ */
+rule "${policyName}.TRANSACTION.COMPLETE.ABATED"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}", getPolicy().getControlLoop().getAbatement() == true)
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == "COMPLETE")
+ $event : VirtualControlLoopEvent(closedLoopControlName == $context.closedLoopControlName, closedLoopEventStatus == ControlLoopEventStatus.ABATED, requestId == $transaction.requestId)
+ $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "ClosedLoop")
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ logger.info("This is ${policyName}.TRANSACTION.COMPLETE.ABATED");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ auditLogger.info(Instant.now() + "Event End: SUCCESS: ABATED");
+ retract($event);
+ retract($transaction);
+ retract($clTimer);
+ $transaction.cleanup();
+
+ sendNotification($context, drools, $transaction.finalNotification());
+ }
+end
+
+/*
+ * We are in the 'COMPLETE' state, meaning no operations are in progress,
+ * and the overall transaction failed, so no ABATED message is expected.
+ */
+rule "${policyName}.TRANSACTION.COMPLETE.FAILED"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName, state == "COMPLETE", finalResultFailure)
+ $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "ClosedLoop")
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ logger.info("This is ${policyName}.TRANSACTION.COMPLETE.FAILED");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ auditLogger.info(Instant.now() + "Transaction End: FAIL: Final Result Failure");
+ retract($transaction);
+ retract($clTimer);
+ $transaction.cleanup();
+
+ sendNotification($context, drools, $transaction.finalNotification());
+ }
+end
+
+/*
+*
+* This is the timer that manages the overall control loop timeout.
+* Due to a bug in the drools code, the drools timer needed to be split from most of the objects in the when clause
+*
+*/
+rule "${policyName}.CLOSED_LOOP.TIMER.FIRED"
+ timer (expr: $timeout)
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, $timeout : delay, !expired, timerType == "ClosedLoop")
+ then
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ logger.info("This is ${policyName}.CLOSED_LOOP.TIMER.FIRED");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ modify($clTimer){setExpired(true)};
+ end
+
+/*
+ * The overall transaction has timed out.
+ */
+rule "${policyName}.TRANSACTION.TIMEOUT"
+ when
+ $context : Context(closedLoopControlName == "${closedLoopControlName}")
+ $transaction: Transaction(closedLoopControlName == $context.closedLoopControlName)
+ $clTimer : ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), expired, timerType == "ClosedLoop")
+ $opTimers : LinkedList()
+ from collect (ControlLoopTimer(closedLoopControlName == $context.closedLoopControlName, requestId == $transaction.getRequestId().toString(), timerType == "Operation"))
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger metricsLogger = $context.getMetricsLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ logger.info("This is ${policyName}.TRANSACTION.TIMEOUT");
+ metricsLogger.info("{} {} {}", Instant.now(), drools.getRule().getName(), drools.getRule().getPackage());
+ auditLogger.info(Instant.now() + "Transaction End: FAIL: Transaction Timeout");
+ retract($transaction);
+ retract($clTimer);
+ // Drools does not support generics, so the time of the objects in opTimers
+ // can't be set to OperationTimer. Therefore, timer must be an Object.
+ for (Object timer : $opTimers) {
+ retract(timer);
+ }
+ $transaction.cleanup();
+ $transaction.clTimeout();
+
+ sendNotification($context, drools, $transaction.finalNotification());
+ }
+end
+
+/* ============================================================ */
+
+/*
+ * This rule is an audit runs at a low priority, and cleans up any unclaimed
+ * 'ControlLoopEvent' instances. The assumption is that all other rules are
+ * structured so that incoming messages will be processed, and retracted
+ * from Drools memory (whether or not they are retained within the
+ * transaction).
+ */
+rule "${policyName}.UNCLAIMED-EVENT"
+ salience -1000
+ when
+ $context : Context()
+ $event : VirtualControlLoopEvent()
+ then
+ {
+ Logger logger = $context.getLogger();
+ Logger auditLogger = $context.getAuditLogger();
+ logger.debug("${policyName}.UNCLAIMED-EVENT: " + $event);
+ auditLogger.info(Instant.now() + "Event End: FAIL: UNCLAIMED-EVENT - See error log");
+ retract($event);
+ }
+end
+
+/*
+ * This rule is an audit runs at a low priority, and cleans up any unclaimed
+ * 'PolicyGuardResponse' instances. The assumption is that all other rules are
+ * structured so that incoming messages will be processed, and retracted
+ * from Drools memory (whether or not they are retained within the
+ * transaction).
+ */
+rule "${policyName}.UNCLAIMED-POLICY-GUARD-RESPONSE"
+ salience -1000
+ when
+ $context : Context()
+ $response : PolicyGuardResponse()
+ then
+ {
+ Logger logger = $context.getLogger();
+ logger.error("${policyName}.UNCLAIMED-POLICY-GUARD-RESPONSE: "
+ + $response);
+ retract($response);
+ }
+end
+
+/*
+ * This rule is an audit runs at a low priority, and cleans up any unclaimed
+ * 'Response' instances. The assumption is that all other rules are
+ * structured so that incoming messages will be processed, and retracted
+ * from Drools memory (whether or not they are retained within the
+ * transaction).
+ */
+rule "${policyName}.UNCLAIMED-LCM-RESPONSE"
+ salience -1000
+ when
+ $context : Context()
+ $response : AppcLcmDmaapWrapper()
+ then
+ {
+ Logger logger = $context.getLogger();
+ logger.error("${policyName}.UNCLAIMED-LCM-RESPONSE: " + $response);
+ retract($response);
+ }
+end
diff --git a/controlloop/m2/test/src/test/resources/appclcm/controller.properties b/controlloop/m2/test/src/test/resources/appclcm/controller.properties
new file mode 100644
index 000000000..e4f1a8c71
--- /dev/null
+++ b/controlloop/m2/test/src/test/resources/appclcm/controller.properties
@@ -0,0 +1,38 @@
+# CONTROLLER section
+controller.name=appclcm
+
+persistence.type=auto
+
+dmaap.source.topics=org.onap.DCAE-APPCLCM-TOPIC
+
+dmaap.source.topics.org.onap.DCAE-APPCLCM-TOPIC.events=org.onap.policy.controlloop.VirtualControlLoopEvent
+dmaap.source.topics.org.onap.DCAE-APPCLCM-TOPIC.events.org.onap.policy.controlloop.VirtualControlLoopEvent.filter=[?($.closedLoopControlName =~ /.*/)]
+dmaap.source.topics.org.onap.DCAE-APPCLCM-TOPIC.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gson
+dmaap.source.topics.org.onap.DCAE-APPCLCM-TOPIC.servers=127.0.71.250
+
+ueb.source.topics=APPC-RESPONSE-APPCLCM-TOPIC
+
+ueb.source.topics.APPC-RESPONSE-APPCLCM-TOPIC.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper
+ueb.source.topics.APPC-RESPONSE-APPCLCM-TOPIC.events.org.onap.policy.appclcm.AppcLcmDmaapWrapper.filter=[?($.type == 'response')]
+ueb.source.topics.APPC-RESPONSE-APPCLCM-TOPIC.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson
+ueb.source.topics.APPC-RESPONSE-APPCLCM-TOPIC.servers=127.0.71.250
+
+ueb.sink.topics=NOTIFICATION-APPCLCM-TOPIC,APPC-REQUEST-APPCLCM-TOPIC
+
+ueb.sink.topics.APPC-REQUEST-APPCLCM-TOPIC.events=org.onap.policy.appclcm.AppcLcmDmaapWrapper
+ueb.sink.topics.APPC-REQUEST-APPCLCM-TOPIC.events.custom.gson=org.onap.policy.appclcm.util.Serialization,gson
+ueb.sink.topics.APPC-REQUEST-APPCLCM-TOPIC.servers=127.0.71.250
+
+ueb.sink.topics.NOTIFICATION-APPCLCM-TOPIC.events=org.onap.policy.controlloop.VirtualControlLoopNotification
+ueb.sink.topics.NOTIFICATION-APPCLCM-TOPIC.events.custom.gson=org.onap.policy.controlloop.util.Serialization,gson
+ueb.sink.topics.NOTIFICATION-APPCLCM-TOPIC.servers=127.0.71.250
+
+guard.javax.persistence.jdbc.driver=org.h2.Driver
+#guard.javax.persistence.jdbc.url=jdbc:h2:file:./H2DB
+#guard.javax.persistence.jdbc.user=sa
+#guard.javax.persistence.jdbc.password=
+#guard.pdp.rest.url=http\://127.0.71.201\:8443/pdp/
+
+rules.artifactId=appclcm
+rules.groupId=org.onap.policy.m2.test
+##rules.version=1.0.0-SNAPSHOT
diff --git a/controlloop/m2/test/src/test/resources/appclcm/kmodule.xml b/controlloop/m2/test/src/test/resources/appclcm/kmodule.xml
new file mode 100644
index 000000000..fc9b66f3e
--- /dev/null
+++ b/controlloop/m2/test/src/test/resources/appclcm/kmodule.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule">
+ <kbase name="rules">
+ <ksession name="appclcm"/>
+ </kbase>
+</kmodule>
diff --git a/controlloop/m2/test/src/test/resources/appclcm/pom.xml b/controlloop/m2/test/src/test/resources/appclcm/pom.xml
new file mode 100644
index 000000000..0cf8b7b92
--- /dev/null
+++ b/controlloop/m2/test/src/test/resources/appclcm/pom.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.onap.policy.m2.test</groupId>
+ <artifactId>appclcm</artifactId>
+ <version>${project.version}</version>
+</project>
diff --git a/controlloop/m2/util/pom.xml b/controlloop/m2/util/pom.xml
new file mode 100644
index 000000000..b453cc59f
--- /dev/null
+++ b/controlloop/m2/util/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!--
+ ============LICENSE_START=======================================================
+ ONAP Policy Engine - Drools PDP
+ ================================================================================
+ Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ ================================================================================
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ============LICENSE_END=========================================================
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onap.policy.drools-applications.controlloop.m2</groupId>
+ <artifactId>m2</artifactId>
+ <version>1.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>util</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.onap.policy.drools-pdp</groupId>
+ <artifactId>policy-core</artifactId>
+ <version>${version.policy.drools-pdp}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/controlloop/m2/util/src/main/java/org/onap/policy/util/DroolsSessionCommonSerializable.java b/controlloop/m2/util/src/main/java/org/onap/policy/util/DroolsSessionCommonSerializable.java
new file mode 100644
index 000000000..d8501fea8
--- /dev/null
+++ b/controlloop/m2/util/src/main/java/org/onap/policy/util/DroolsSessionCommonSerializable.java
@@ -0,0 +1,136 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * util
+ * ================================================================================
+ * 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.util;
+
+import java.io.InvalidObjectException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.HashMap;
+
+import org.onap.policy.drools.core.PolicySession;
+
+/**
+ * This class provides a way to serialize/deserialize objects by locating
+ * an already existing object on the remote end, and using that instead. It
+ * is useful for objects that may be shared by multiple transactions.
+ */
+public class DroolsSessionCommonSerializable implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ // identifies an object within a Drools session
+ private String name;
+
+ // the object is serialized, but is only used if the corresponding object
+ // on the remote end can't be located for some reason
+ private Object object;
+
+ /**
+ * Constructor - initialize instance, and also store a reference to
+ * the object in a 'PolicySession' adjunct.
+ *
+ * @param name identifies the object within the Drools session
+ * @param object the shared object
+ */
+ public DroolsSessionCommonSerializable(String name, Object object) {
+ this.name = name;
+ this.object = object;
+
+ // store a reference to the object within the adjunct
+ // (only works if we can locate the Drools session)
+ Adjunct adjunct = getAdjunct();
+ if (adjunct != null) {
+ adjunct.put(name, object);
+ }
+ }
+
+ /**
+ * Return this object as a String.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "DroolsSessionCommonSerializable[" + name + "]";
+ }
+
+ /**
+ * This method runs as part of deserialization. If we are able to locate
+ * the 'PolicySession' and adjunct, and fetch the replacement object from
+ * the adjunct, that is used. Otherwise, the deserialized object is used
+ * (which is likely a duplicate).
+ *
+ * @return the local named object (if available), or the deserialized
+ * object
+ */
+ private Object readResolve() throws ObjectStreamException {
+ Adjunct adjunct = getAdjunct();
+ Object replacementObject;
+
+ if (adjunct != null && (replacementObject = adjunct.get(name)) != null) {
+ // we found the adjunct, as well as the replacement object -- use
+ // the replacement object
+ return replacementObject;
+ }
+
+ // either we couldn't find the adjunct, or couldn't locate the object
+ // within the adjunct
+ return object;
+ }
+
+ /**
+ * This method will:
+ * 1) Locate the 'PolicySession' (only works from within the Drools thread),
+ * 2) Find or create the adjunct.
+ *
+ * @return the adjunct, or 'null' if we aren't running within a
+ * Drools session
+ */
+ private Adjunct getAdjunct() {
+ // PolicySession - this only works from within the Drools thread
+ PolicySession session = PolicySession.getCurrentSession();
+ Adjunct adjunct = null;
+ if (session != null) {
+ // we found the 'PolicySession' -- now, look for the adjunct
+ Object adj = session.getAdjunct(Adjunct.class);
+ if (adj == null || !(adj instanceof Adjunct)) {
+ // adjunct does not exist, or has the wrong type -- create it
+ adjunct = new Adjunct();
+ session.setAdjunct(Adjunct.class, adjunct);
+ } else {
+ // found the adjunct -- return it
+ adjunct = (Adjunct)adj;
+ //adjunct = Adjunct.class.cast(adj);
+ }
+ }
+ return adjunct;
+ }
+
+ /* ============================================================ */
+
+ /**
+ * While 'HashMap&lt;String, Object&gt;' could be used directly instead of defining
+ * a subclass, you can't do run-time type checking of a parameterized type.
+ * As a result, the 'getAdjunct' method (above) would get compile-time
+ * warnings.
+ */
+ private static class Adjunct extends HashMap<String, Object> {
+ }
+}
diff --git a/controlloop/m2/util/src/test/java/org/onap/policy/util/DroolsSessionCommonSerializableTest.java b/controlloop/m2/util/src/test/java/org/onap/policy/util/DroolsSessionCommonSerializableTest.java
new file mode 100644
index 000000000..520158b8c
--- /dev/null
+++ b/controlloop/m2/util/src/test/java/org/onap/policy/util/DroolsSessionCommonSerializableTest.java
@@ -0,0 +1,36 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * util
+ * ================================================================================
+ * 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.util;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.junit.Test;
+
+public class DroolsSessionCommonSerializableTest {
+
+ @Test
+ public void test() {
+ Object object = new Object();
+ DroolsSessionCommonSerializable droolsSessionCommonSerializable =
+ new DroolsSessionCommonSerializable("drools", object);
+ assertNotNull(droolsSessionCommonSerializable.toString());
+ }
+}
diff --git a/controlloop/pom.xml b/controlloop/pom.xml
index 734d79d7c..b2a73b9c3 100644
--- a/controlloop/pom.xml
+++ b/controlloop/pom.xml
@@ -31,6 +31,7 @@
<modules>
<module>common</module>
<module>templates</module>
+ <module>m2</module>
<module>packages</module>
</modules>
</project>