From 3e05cb41202145e113853392e9837abf3f6ec12c Mon Sep 17 00:00:00 2001 From: "Straubs, Ralph (rs8887)" Date: Tue, 19 Nov 2019 04:11:23 -0600 Subject: Add m2 model, including the LCM application Issue-ID: POLICY-1948 Change-Id: I18a5231d3102073c928a591c9e91b241b7093680 Signed-off-by: Straubs, Ralph (rs8887) --- controlloop/m2/adapters/pom.xml | 46 ++ .../policy/m2/adapters/VirtualOnsetAdapter.java | 62 ++ .../m2/adapters/VirtualOnsetAdapterTest.java | 53 ++ controlloop/m2/appclcm/pom.xml | 86 +++ .../org/onap/policy/m2/appclcm/AppcLcmActor.java | 76 +++ .../m2/appclcm/AppcLcmHealthCheckOperation.java | 248 +++++++ .../onap/policy/m2/appclcm/AppcLcmOperation.java | 703 ++++++++++++++++++++ .../m2/appclcm/model/AppcLcmResponseCode.java | 58 ++ .../services/org.onap.policy.m2.base.Actor | 1 + .../appclcm/AppcLcmHealthCheckOperationTest.java | 281 ++++++++ .../test/java/appclcm/AppcLcmOperationTest.java | 708 ++++++++++++++++++++ .../test/java/model/AppcLcmResponseCodeTest.java | 44 ++ controlloop/m2/base/pom.xml | 105 +++ .../main/java/org/onap/policy/m2/base/Actor.java | 52 ++ .../java/org/onap/policy/m2/base/GuardAdjunct.java | 123 ++++ .../java/org/onap/policy/m2/base/OnsetAdapter.java | 156 +++++ .../java/org/onap/policy/m2/base/Operation.java | 125 ++++ .../java/org/onap/policy/m2/base/Transaction.java | 717 +++++++++++++++++++++ .../main/java/org/onap/policy/m2/base/Util.java | 62 ++ .../onap/policy/m2/base/ActorOperationTest.java | 116 ++++ .../org/onap/policy/m2/base/GuardAdjunctTest.java | 103 +++ .../org/onap/policy/m2/base/TransactionTest.java | 230 +++++++ .../java/org/onap/policy/m2/base/UtilTest.java | 73 +++ controlloop/m2/feature-controlloop-m2/pom.xml | 126 ++++ .../src/assembly/assemble_zip.xml | 80 +++ controlloop/m2/guard/pom.xml | 84 +++ .../java/org/onap/policy/guard/GuardContext.java | 400 ++++++++++++ .../org/onap/policy/guard/GuardContextTest.java | 139 ++++ controlloop/m2/lock/pom.xml | 55 ++ .../onap/policy/drools/m2/lock/LockAdjunct.java | 164 +++++ .../policy/drools/m2/lock/LockAdjunctTest.java | 97 +++ controlloop/m2/pom.xml | 49 ++ controlloop/m2/test/pom.xml | 100 +++ .../java/org/onap/policy/m2/test/AppcLcmTest.java | 318 +++++++++ .../java/org/onap/policy/m2/test/SimDmaap.java | 266 ++++++++ .../java/org/onap/policy/m2/test/SimGuard.java | 65 ++ .../test/java/org/onap/policy/m2/test/Util.java | 502 +++++++++++++++ .../appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml | 83 +++ .../appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl | 649 +++++++++++++++++++ .../test/resources/appclcm/controller.properties | 38 ++ .../m2/test/src/test/resources/appclcm/kmodule.xml | 26 + .../m2/test/src/test/resources/appclcm/pom.xml | 29 + controlloop/m2/util/pom.xml | 47 ++ .../util/DroolsSessionCommonSerializable.java | 136 ++++ .../util/DroolsSessionCommonSerializableTest.java | 36 ++ controlloop/pom.xml | 1 + 46 files changed, 7718 insertions(+) create mode 100644 controlloop/m2/adapters/pom.xml create mode 100644 controlloop/m2/adapters/src/main/java/org/onap/policy/m2/adapters/VirtualOnsetAdapter.java create mode 100644 controlloop/m2/adapters/src/test/java/org/onap/policy/m2/adapters/VirtualOnsetAdapterTest.java create mode 100644 controlloop/m2/appclcm/pom.xml create mode 100644 controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java create mode 100644 controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java create mode 100644 controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java create mode 100644 controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java create mode 100644 controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor create mode 100644 controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmHealthCheckOperationTest.java create mode 100644 controlloop/m2/appclcm/src/test/java/appclcm/AppcLcmOperationTest.java create mode 100644 controlloop/m2/appclcm/src/test/java/model/AppcLcmResponseCodeTest.java create mode 100644 controlloop/m2/base/pom.xml create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/OnsetAdapter.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Operation.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Transaction.java create mode 100644 controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Util.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/ActorOperationTest.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/GuardAdjunctTest.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/TransactionTest.java create mode 100644 controlloop/m2/base/src/test/java/org/onap/policy/m2/base/UtilTest.java create mode 100644 controlloop/m2/feature-controlloop-m2/pom.xml create mode 100644 controlloop/m2/feature-controlloop-m2/src/assembly/assemble_zip.xml create mode 100644 controlloop/m2/guard/pom.xml create mode 100644 controlloop/m2/guard/src/main/java/org/onap/policy/guard/GuardContext.java create mode 100644 controlloop/m2/guard/src/test/java/org/onap/policy/guard/GuardContextTest.java create mode 100644 controlloop/m2/lock/pom.xml create mode 100644 controlloop/m2/lock/src/main/java/org/onap/policy/drools/m2/lock/LockAdjunct.java create mode 100644 controlloop/m2/lock/src/test/java/org/onap/policy/drools/m2/lock/LockAdjunctTest.java create mode 100644 controlloop/m2/pom.xml create mode 100644 controlloop/m2/test/pom.xml create mode 100644 controlloop/m2/test/src/test/java/org/onap/policy/m2/test/AppcLcmTest.java create mode 100644 controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimDmaap.java create mode 100644 controlloop/m2/test/src/test/java/org/onap/policy/m2/test/SimGuard.java create mode 100644 controlloop/m2/test/src/test/java/org/onap/policy/m2/test/Util.java create mode 100644 controlloop/m2/test/src/test/resources/appclcm/CLRulevUSPAPPCLCMGuardTemplate.yaml create mode 100644 controlloop/m2/test/src/test/resources/appclcm/M2CLRulevUSPAPPCLCMGuardTemplate.drl create mode 100644 controlloop/m2/test/src/test/resources/appclcm/controller.properties create mode 100644 controlloop/m2/test/src/test/resources/appclcm/kmodule.xml create mode 100644 controlloop/m2/test/src/test/resources/appclcm/pom.xml create mode 100644 controlloop/m2/util/pom.xml create mode 100644 controlloop/m2/util/src/main/java/org/onap/policy/util/DroolsSessionCommonSerializable.java create mode 100644 controlloop/m2/util/src/test/java/org/onap/policy/util/DroolsSessionCommonSerializableTest.java 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 @@ + + + + + 4.0.0 + + org.onap.policy.drools-applications.controlloop.m2 + m2 + 1.6.0-SNAPSHOT + + adapters + adapters + + + + junit + junit + test + + + + org.onap.policy.drools-applications.controlloop.m2 + base + ${project.version} + + + 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 @@ + + + + + 4.0.0 + + + org.onap.policy.drools-applications.controlloop.m2 + m2 + 1.6.0-SNAPSHOT + + + appclcm + Experimental Control Loop Model - appclcm + + + + org.onap.policy.drools-applications.controlloop.m2 + adapters + ${project.version} + + + + org.onap.policy.drools-applications.controlloop.m2 + base + ${project.version} + + + + org.onap.policy.drools-applications.controlloop.m2 + lock + ${project.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + appclcm + ${policy.models.version} + + + + org.onap.policy.drools-pdp + policy-management + ${version.policy.drools-pdp} + provided + + + + org.projectlombok + lombok + provided + + + + org.powermock + powermock-api-mockito + test + + + + junit + junit + test + + + + 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 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 aai, String recipe) throws ControlLoopException { + Map 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 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 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 aai, String recipe) throws ControlLoopException { + Map 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 setCommonPayload(Map aai) throws ControlLoopException { + Map payload = new HashMap<>(); + + for (Map.Entry 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 setRebootPayload() throws ControlLoopException { + Map 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 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 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()); + 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()); + 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 @@ + + + + + 4.0.0 + + + org.onap.policy.drools-applications.controlloop.m2 + m2 + 1.6.0-SNAPSHOT + + + base + Experimental Control Loop Model - base + + + + + org.onap.policy.drools-applications.controlloop.m2 + guard + ${project.version} + + + + org.onap.policy.drools-applications.controlloop.common + eventmanager + ${project.version} + + + + org.onap.policy.models.policy-models-interactions + model-yaml + ${policy.models.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + events + ${policy.models.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + aai + ${policy.models.version} + + + + org.onap.policy.models.policy-models-interactions.model-impl + sdc + ${policy.models.version} + + + + org.onap.policy.drools-pdp + policy-management + ${version.policy.drools-pdp} + provided + + + + org.projectlombok + lombok + provided + + + + org.powermock + powermock-api-mockito + test + + + + org.powermock + powermock-module-junit4 + test + + + + junit + junit + test + + + + diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java new file mode 100644 index 000000000..00eb181fc --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/Actor.java @@ -0,0 +1,52 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.m2.base; + +import org.onap.policy.controlloop.ControlLoopEvent; +import org.onap.policy.controlloop.policy.Policy; + +/** + * This is the 'Actor' interface -- objects implementing this interface + * are placed within the 'nameToActor' table within class 'Transaction'. + * All of the instances are created and inserted in the table at initialization + * time, and are located using the 'Policy.actor' field as a key. + */ +public interface Actor { + /** + * Return the name associated with this Actor. + * + * @return the name associated with this Actor (as it appears in the 'yaml') + */ + String getName(); + + /** + * Create an operation for this actor, based on the supplied policy. + * + * @param transaction the transaction the operation is running under + * @param policy the policy associated with this operation + * @param onset the initial onset event that triggered the transaction + * @param attempt this value starts at 1, and is incremented for each retry + * @return the Operation instance + */ + Operation createOperation( + Transaction transaction, Policy policy, ControlLoopEvent onset, + int attempt); +} diff --git a/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java new file mode 100644 index 000000000..34397e2b9 --- /dev/null +++ b/controlloop/m2/base/src/main/java/org/onap/policy/m2/base/GuardAdjunct.java @@ -0,0 +1,123 @@ +/*- + * ============LICENSE_START======================================================= + * m2/base + * ================================================================================ + * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.m2.base; + +import java.io.Serializable; + +import org.onap.policy.controlloop.ControlLoopOperation; +import org.onap.policy.controlloop.policy.Policy; +import org.onap.policy.guard.GuardContext; + +/** + * This adjunct class provides a way of accessing 'GuardContext' for any + * operations that use 'guard'. It needs to be created and inserted into the + * transaction 'adjunct' list prior to creating any operations that use it. + * + *

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

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

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