aboutsummaryrefslogtreecommitdiffstats
path: root/controlloop/m2/appclcm/src/main
diff options
context:
space:
mode:
authorStraubs, Ralph (rs8887) <rs8887@att.com>2019-11-19 04:11:23 -0600
committerStraubs, Ralph (rs8887) <rs8887@att.com>2020-01-10 03:20:23 -0600
commit3e05cb41202145e113853392e9837abf3f6ec12c (patch)
tree0c504018436c3933f563caa37c3ea0512c82181e /controlloop/m2/appclcm/src/main
parent927c7c177670a812a4a4139281ef84e85b520645 (diff)
Add m2 model, including the LCM application
Issue-ID: POLICY-1948 Change-Id: I18a5231d3102073c928a591c9e91b241b7093680 Signed-off-by: Straubs, Ralph (rs8887) <rs8887@att.com>
Diffstat (limited to 'controlloop/m2/appclcm/src/main')
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java76
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java248
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java703
-rw-r--r--controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java58
-rw-r--r--controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor1
5 files changed, 1086 insertions, 0 deletions
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java
new file mode 100644
index 000000000..f89d3b873
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmActor.java
@@ -0,0 +1,76 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.m2.appclcm;
+
+import java.io.Serializable;
+
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+
+import org.onap.policy.m2.adapters.VirtualOnsetAdapter;
+import org.onap.policy.m2.base.Actor;
+import org.onap.policy.m2.base.Operation;
+import org.onap.policy.m2.base.Transaction;
+
+/**
+ * A single instance of this class is created, and resides within the
+ * 'nameToActor' table within class 'Transaction', under the key 'APPC'.
+ */
+public class AppcLcmActor implements Actor, Serializable {
+ /* *******************/
+ /* 'Actor' interface */
+ /* *******************/
+
+ private static final long serialVersionUID = -593438898257647144L;
+
+
+ static {
+ // ensures that 'VirtualOnsetAdapter' has an entry in the
+ // 'OnsetAdapter' table
+ VirtualOnsetAdapter.register();
+ }
+
+ /**
+ * Return the name associated with this 'Actor'.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
+ return "APPCLCM";
+ }
+
+ /**
+ * Create an 'Operation' for this 'Actor'.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public Operation createOperation(
+ Transaction transaction, Policy policy, ControlLoopEvent onset,
+ int attempt) {
+
+ if ("healthcheck".equalsIgnoreCase(policy.getRecipe())) {
+ return new AppcLcmHealthCheckOperation(transaction, policy, onset, attempt);
+ }
+ return new AppcLcmOperation(transaction, policy, onset, attempt);
+ }
+}
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java
new file mode 100644
index 000000000..42e06f98f
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmHealthCheckOperation.java
@@ -0,0 +1,248 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.m2.appclcm;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmOutput;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.guard.PolicyGuardResponse;
+import org.onap.policy.m2.appclcm.model.AppcLcmResponseCode;
+import org.onap.policy.m2.base.Transaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AppcLcmHealthCheckOperation extends AppcLcmOperation {
+ public static final String DCAE_IPV4_ADDR =
+ "vserver.l-interface.l3-interface-ipv4-address-list.l3-inteface-ipv4-address";
+
+ private static Logger logger = LoggerFactory.getLogger(AppcLcmHealthCheckOperation.class);
+
+ private static final long serialVersionUID = 4969322301462776173L;
+
+ public AppcLcmHealthCheckOperation(Transaction transaction, Policy policy,
+ ControlLoopEvent onset, int attempt) {
+ super(transaction, policy, onset, attempt);
+ }
+
+ /**
+ * This method will attempt to deserialize the json payload from appc and
+ * then parse the response to determine if the vnf is healthy or unhealthy.
+ * The "state" field in the payload will contain "healthy" or "unhealthy"
+ * based on the condition of the vnf.
+ *
+ * @param jsonPayload
+ * the appc lcm response json payload
+ * @return the string that contains the state of the vnf
+ */
+ private String getVnfHealthState(String jsonPayload) {
+ HashMap<String, Object> healthCheckPayloadMap;
+ try {
+ healthCheckPayloadMap = coder.decode(jsonPayload, HashMap.class);
+ } catch (CoderException e) {
+ return null;
+ }
+
+ String stateOfHealth = null;
+ if (healthCheckPayloadMap.containsKey("state")) {
+ stateOfHealth = healthCheckPayloadMap.get("state").toString();
+ } else {
+ return null;
+ }
+ return stateOfHealth;
+ }
+
+ /**
+ * An incoming message is being delivered to the operation.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void incomingMessage(Object object) {
+ if (!(object instanceof AppcLcmDmaapWrapper)) {
+ if (object instanceof PolicyGuardResponse) {
+ incomingGuardMessage((PolicyGuardResponse) object);
+ return;
+ }
+ // ignore this message (not sure why we even got it)
+ return;
+ }
+
+ // If we reach this point, we have a 'AppcLcmDmaapWrapper' instance.
+ // The rest of this method is mostly copied from
+ // 'ControlLoopOperationManager.onResponse'.
+
+ AppcLcmOutput response = ((AppcLcmDmaapWrapper)object).getBody().getOutput();
+
+ //
+ // Determine which subrequestID (ie. attempt)
+ //
+ int operationAttempt;
+ try {
+ operationAttempt = Integer
+ .parseInt(response.getCommonHeader().getSubRequestId());
+ } catch (NumberFormatException e) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ //
+ this.completeOperation(
+ this.getAttempt(),
+ "Policy was unable to parse APP-C SubRequestID (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Sanity check the response message
+ //
+ if (response.getStatus() == null) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ //
+ this.completeOperation(
+ operationAttempt,
+ "Policy was unable to parse APP-C response status field (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Get the Response Code
+ //
+ AppcLcmResponseCode responseValue = AppcLcmResponseCode
+ .toResponseValue(response.getStatus().getCode());
+ if (responseValue == null) {
+ //
+ // We are unaware of this code
+ //
+ this.completeOperation(
+ operationAttempt,
+ "Policy was unable to parse APP-C response status code field.",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Ok, let's figure out what APP-C's response is
+ //
+ switch (responseValue) {
+ case ACCEPTED:
+ //
+ // This is good, they got our original message and
+ // acknowledged it.
+ //
+ // Is there any need to track this?
+ //
+ return;
+ case ERROR:
+ case REJECT:
+ //
+ // We'll consider these two codes as exceptions
+ //
+ this.completeOperation(operationAttempt,
+ response.getStatus().getMessage(),
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ case FAILURE:
+ //
+ // APPC could not do a healthcheck
+ //
+ this.completeOperation(operationAttempt,
+ response.getStatus().getMessage(),
+ PolicyResult.FAILURE);
+ return;
+ case SUCCESS:
+ //
+ // This means APPC was able to execute the health check.
+ // The payload has to be parsed to see if the VNF is
+ // healthy or unhealthy
+ //
+
+ //
+ // sanity check the payload
+ //
+ if (response.getPayload() == null || response.getPayload().isEmpty()) {
+ //
+ // We are cannot parse the payload
+ //
+ this.completeOperation(
+ operationAttempt,
+ "Policy was unable to parse APP-C response payload because it was null.",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+
+ //
+ // parse the payload to see if the VNF is healthy/unhealthy
+ //
+ String vnfHealthState = getVnfHealthState(response.getPayload());
+ if ("healthy".equalsIgnoreCase(vnfHealthState)) {
+ this.completeOperation(operationAttempt, "VNF is healthy",
+ PolicyResult.SUCCESS);
+ } else if ("unhealthy".equalsIgnoreCase(vnfHealthState)) {
+ this.completeOperation(operationAttempt, "VNF is unhealthy",
+ PolicyResult.FAILURE);
+ } else {
+ this.completeOperation(
+ operationAttempt,
+ "Error: Could not determine the state of the VNF."
+ + " The state field in the APPC response payload was unrecognized or null.",
+ PolicyResult.FAILURE_EXCEPTION);
+ }
+ return;
+ default:
+ return;
+ }
+ }
+
+ /**
+ * This method will construct a payload for a health check.
+ * The payload must be an escaped json string so gson is used
+ * to convert the payload hashmap into json
+ *
+ * @return an escaped json string representation of the payload
+ * @throws ControlLoopException if it occurs
+ */
+ @Override
+ protected String setPayload(Map<String, String> aai, String recipe) throws ControlLoopException {
+ Map<String, String> payload = new HashMap<>();
+
+ // Extract oam ip address from the onset
+ String ipAddr = aai.get(DCAE_IPV4_ADDR);
+ if (ipAddr != null) {
+ payload.put("host-ip-address", ipAddr);
+ } else {
+ logger.error("Error - IPv4 Address not found in the onset");
+ setErrorStatus("Error - IPv4 Address not found in the onset");
+ }
+
+ try {
+ return coder.encode(payload);
+ } catch (CoderException e) {
+ return null;
+ }
+ }
+}
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java
new file mode 100644
index 000000000..6a2518f46
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/AppcLcmOperation.java
@@ -0,0 +1,703 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.m2.appclcm;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+
+import org.onap.policy.appclcm.AppcLcmBody;
+import org.onap.policy.appclcm.AppcLcmCommonHeader;
+import org.onap.policy.appclcm.AppcLcmDmaapWrapper;
+import org.onap.policy.appclcm.AppcLcmInput;
+import org.onap.policy.appclcm.AppcLcmOutput;
+import org.onap.policy.common.utils.coder.CoderException;
+import org.onap.policy.common.utils.coder.StandardCoder;
+import org.onap.policy.controlloop.ControlLoopEvent;
+import org.onap.policy.controlloop.ControlLoopException;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+import org.onap.policy.controlloop.policy.TargetType;
+import org.onap.policy.drools.m2.lock.LockAdjunct;
+import org.onap.policy.guard.PolicyGuardResponse;
+import org.onap.policy.m2.appclcm.model.AppcLcmResponseCode;
+import org.onap.policy.m2.base.GuardAdjunct;
+import org.onap.policy.m2.base.Operation;
+import org.onap.policy.m2.base.Transaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class is used for all APPC LCM operations. The only difference between
+ * operation types (Restart, Rebuild, Migrate, Evacuate, or HealthCheck) as
+ * far as DroolsPDP is concerned, is the operation name (policy.recipe).
+ * It is up to APPC to interpret these operations.
+ */
+public class AppcLcmOperation implements Operation, LockAdjunct.Requestor, Serializable {
+ public static final String DCAE_CLOSEDLOOP_DISABLED_FIELD = "vserver.is-closed-loop-disabled";
+ public static final String DCAE_VSERVER_SELF_LINK_FIELD = "vserver.selflink";
+ public static final String DCAE_IDENTITY_FIELD = "cloud-region.identity-url";
+ public static final String DCAE_VNF_NAME_FIELD = "generic-vnf.vnf-name";
+ public static final String DCAE_VNF_ID_FIELD = "generic-vnf.vnf-id";
+ public static final String DCAE_VSERVER_ID_FIELD = "vserver.vserver-id";
+ public static final String DCAE_TENANT_ID_FIELD = "tenant.tenant-id";
+
+ public static final String APPC_LCM_VM_ID_FIELD = "vm-id";
+ public static final String APPC_LCM_IDENTITY_URL_FIELD = "identity-url";
+ public static final String APPC_LCM_TENANT_ID_FIELD = "tenant-id";
+
+ private static Logger logger = LoggerFactory.getLogger(AppcLcmOperation.class);
+
+ private static final long serialVersionUID = 5062964240000304989L;
+
+ // state when waiting for a lock
+ public static final String LCM_WAIT_FOR_LOCK = "LCM.WAIT_FOR_LOCK";
+
+ // state when waiting for a response from 'guard'
+ public static final String LCM_GUARD_PENDING = "LCM.GUARD_PENDING";
+
+ // state when ready to send out the LCM message
+ public static final String LCM_BEGIN = "LCM.BEGIN";
+
+ // state when waiting for a response from APPC
+ public static final String LCM_PENDING = "LCM.PENDING";
+
+ // state when processing can't continue due to errors
+ public static final String LCM_ERROR = "LCM.ERROR";
+
+ // state when the operation has completed (success, failure, or timeout)
+ public static final String LCM_COMPLETE = "LCM.COMPLETE";
+
+ // the APPC LCM recipes supported by Policy
+ private static final ImmutableList<String> recipes = ImmutableList.of(
+ "Restart", "Rebuild", "Migrate", "Evacuate",
+ "HealthCheck", "Reboot", "Start", "Stop");
+
+ // used for JSON <-> String conversion
+ protected static StandardCoder coder = new StandardCoder();
+
+ // current state -- one of the 6 values, above
+ @Getter(onMethod = @__({@Override}))
+ private String state;
+
+ // transaction associated with this operation
+ private Transaction transaction;
+
+ // policy associated with this operation
+ @Getter(onMethod = @__({@Override}))
+ private Policy policy;
+
+ // initial onset message
+ private VirtualControlLoopEvent onset;
+
+ // attempt associated with this operation
+ @Getter(onMethod = @__({@Override}))
+ private int attempt;
+
+ // message, if any, associated with the result of this operation
+ @Getter(onMethod = @__({@Override}))
+ private String message = null;
+
+ // operation result -- set to a non-null value when the operation completes
+ @Getter(onMethod = @__({@Override}))
+ private PolicyResult result = null;
+
+ // the APPC LCM 'target' derived from the onset
+ private String target;
+
+ // reference to a Transaction adjunct supporting guard operations
+ private GuardAdjunct guardAdjunct;
+
+ // counter for how many partial failures were received from appc
+ private int partialFailureCount = 0;
+
+ // counter for how many partial success were received from appc
+ private int partialSuccessCount = 0;
+
+ /**
+ * Constructor -- initialize an LCM operation instance,
+ * try to acquire a lock, and start the guard query if we are ready.
+ *
+ * @param transaction the transaction the operation is running under
+ * @param policy the policy associated with this operation
+ * @param onset the initial onset event that triggered the transaction
+ * @param attempt this value starts at 1, and is incremented for each retry
+ */
+ public AppcLcmOperation(Transaction transaction, Policy policy, ControlLoopEvent onset,
+ int attempt) {
+ // state prior to aquiring the lock
+ // (will be changed when the lock is acquired)
+ this.state = LCM_WAIT_FOR_LOCK;
+ this.transaction = transaction;
+ this.policy = policy;
+ this.attempt = attempt;
+
+ if (!(onset instanceof VirtualControlLoopEvent)) {
+ // we need the correct 'onset' event type
+ state = LCM_COMPLETE;
+ result = PolicyResult.FAILURE;
+ message = "Onset event has the wrong type";
+ return;
+ }
+
+ this.onset = (VirtualControlLoopEvent)onset;
+
+ // fetch or create the guard adjunct -- note that 'guard' operations are
+ // only performed if a 'GuardContext' is present, and the adjunct was
+ // created by the Drools rules prior to creating this operation
+ this.guardAdjunct = transaction.getAdjunct(GuardAdjunct.class);
+
+ // attempt to get a lock for the VM -- if we get it immediately,
+ // we can go to the 'LCM_GUARD_PENDING' or 'LCM_BEGIN' state
+
+ target = this.onset.getAai().get(onset.getTarget()).toString();
+ String key = onset.getTargetType() + ":" + target;
+ if (transaction.getAdjunct(LockAdjunct.class).getLock(this, key,
+ transaction.getRequestId().toString(), false)) {
+ // lock was acquired immediately -- move on to the 'guard query'
+ // phase
+ guardQuery();
+ }
+ }
+
+ /**
+ * A method returning true if the A&AI subtag exists
+ * and the control loop exists and is not disabled and
+ * the target field exists as a key in the A&AI subtag.
+ *
+ * @param transaction the transaction corresponding to an event
+ * @param event the onset containing the A&AI subtag
+ * @return true if the A&AI subtag is valid, false otherwise
+ */
+ public static boolean isAaiValid(Transaction transaction, VirtualControlLoopEvent event) {
+ if (event.getAai() == null) {
+ transaction.setNotificationMessage("No A&AI Subtag");
+ return false;
+ } else if (!event.getAai().containsKey(DCAE_CLOSEDLOOP_DISABLED_FIELD)) {
+ transaction.setNotificationMessage(DCAE_CLOSEDLOOP_DISABLED_FIELD
+ + " information missing");
+ return false;
+ } else if (isClosedLoopDisabled(event.getAai())) {
+ transaction.setNotificationMessage(DCAE_CLOSEDLOOP_DISABLED_FIELD
+ + " is set to true");
+ return false;
+ } else if (!event.getAai().containsKey(event.getTarget())) {
+ transaction.setNotificationMessage("target field invalid - must have corresponding AAI value");
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isClosedLoopDisabled(Map<String, String> map) {
+ if (!map.containsKey(DCAE_CLOSEDLOOP_DISABLED_FIELD)) {
+ return false;
+ }
+ String disabled = map.get(DCAE_CLOSEDLOOP_DISABLED_FIELD);
+ return ("true".equalsIgnoreCase(disabled) || "y".equalsIgnoreCase(disabled));
+ }
+
+ /**
+ * trigger an asynchronous guard query -- if guard is not enabled,
+ * we go directly to the 'LCM_BEGIN' state.
+ */
+ private void guardQuery() {
+ if (guardAdjunct.asyncQuery(policy, target, onset.getRequestId().toString())) {
+ // 'GuardContext' is available --
+ // wait for an incoming 'PolicyGuardResponse' message
+ this.state = LCM_GUARD_PENDING;
+ } else {
+ // no 'GuardContext' is available -- go directly to the 'begin' state
+ this.state = LCM_BEGIN;
+ transaction.modify();
+ }
+ }
+
+ /*=====================================*/
+ /* 'LockAdjunct.Requestor' interface */
+ /*=====================================*/
+
+ /**
+ * This method is called by 'LockAdjunct' if we initially had to wait for
+ * the lock, but it has now became available.
+ */
+ public void lockAvailable() {
+ if (this.state == LCM_WAIT_FOR_LOCK) {
+ // we have the lock -- invoke 'quardQuery()',
+ // go to the appropriate state, and mark the transaction as modified
+ guardQuery();
+
+ // the 'lockAvailable' method was presumably triggered by the
+ // release
+ // of the lock by an unrelated transaction -- 'transaction.modify'
+ // is
+ // called to let Drools know that our transaction has gone through a
+ // state change
+ transaction.modify();
+ }
+ }
+
+ /**
+ * This method is called by 'LockAdjunct' if the lock was unable to be
+ * obtained.
+ */
+ public void lockUnavailable() {
+ if (this.state == LCM_WAIT_FOR_LOCK) {
+ try {
+ setErrorStatus("Already processing event with this target");
+ } catch (ControlLoopException e) {
+ logger.debug("Lock could not be obtained for this operation");
+ }
+ }
+ }
+
+ /*=======================*/
+ /* 'Operation' interface */
+ /*=======================*/
+
+ /**
+ * This method maps the recipe to the correct rpc-name syntax.
+ */
+ private String toRpcName(String recipe) {
+ String rpcName = recipe.toLowerCase();
+ if ("healthcheck".equals(rpcName)) {
+ rpcName = "health-check";
+ }
+ return rpcName;
+ }
+
+ /**
+ * This method forwards the construction of the recipe's
+ * payload to the proper handler.
+ *
+ * @return a json representation of the payload
+ * @throws ControlLoopException if it occurs
+ */
+ protected String setPayload(Map<String, String> aai, String recipe) throws ControlLoopException {
+ Map<String, String> payload = null;
+
+ switch (recipe) {
+ case "restart":
+ case "rebuild":
+ case "migrate":
+ case "evacuate":
+ case "start":
+ case "stop":
+ if (this.policy.getTarget().getType() == TargetType.VM) {
+ payload = setCommonPayload(aai);
+ }
+ break;
+ case "reboot":
+ payload = setRebootPayload();
+ break;
+ default:
+ payload = null;
+ break;
+ }
+
+ if (payload == null) {
+ return null;
+ }
+
+ try {
+ return coder.encode(payload);
+ } catch (CoderException e) {
+ return null;
+ }
+ }
+
+ /**
+ * This method will construct a payload for a restart, rebuild,
+ * migrate, or evacuate. The payload must be an escaped json
+ * string so gson is used to convert the payload hashmap into
+ * json
+ *
+ * @return a hashmap representation of the payload
+ * @throws ControlLoopException if it occurs
+ */
+ private Map<String, String> setCommonPayload(Map<String, String> aai) throws ControlLoopException {
+ Map<String, String> payload = new HashMap<>();
+
+ for (Map.Entry<String, String> entry : aai.entrySet()) {
+ switch (entry.getKey()) {
+ case DCAE_VSERVER_SELF_LINK_FIELD:
+ if (entry.getValue() != null) {
+ payload.put(APPC_LCM_VM_ID_FIELD, entry.getValue());
+ } else {
+ setErrorStatus("dcae onset is missing " + DCAE_VSERVER_SELF_LINK_FIELD);
+ }
+ break;
+ case DCAE_IDENTITY_FIELD:
+ if (entry.getValue() != null) {
+ payload.put(APPC_LCM_IDENTITY_URL_FIELD, entry.getValue());
+ } else {
+ setErrorStatus("dcae onset is missing " + DCAE_IDENTITY_FIELD);
+ }
+ break;
+ case DCAE_TENANT_ID_FIELD:
+ if (entry.getValue() != null) {
+ payload.put(APPC_LCM_TENANT_ID_FIELD, entry.getValue());
+ } else {
+ setErrorStatus("dcae onset is missing " + DCAE_TENANT_ID_FIELD);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return payload;
+ }
+
+ /**
+ * This method will construct a payload for a reboot.
+ * The payload must be an escaped json string so gson is used
+ * to convert the payload hashmap into json. The reboot payload
+ * requires a type of "HARD" or "SOFT" reboot from the policy
+ * defined through CLAMP.
+ *
+ * @return an escaped json string representation of the payload
+ */
+ private Map<String, String> setRebootPayload() throws ControlLoopException {
+ Map<String, String> payload = new HashMap<>();
+
+ if (this.policy.getTarget().getType() == TargetType.VM) {
+ payload = setCommonPayload(onset.getAai());
+ // The tenant-id is not used for the reboot request so we can remove
+ // it after being added by the common payload
+ payload.remove(APPC_LCM_TENANT_ID_FIELD);
+ }
+
+ // Extract "HARD" or "SOFT" from YAML policy
+ String type = this.policy.getPayload().get("type").toUpperCase();
+ payload.put("type", type);
+
+ return payload;
+ }
+
+ /**
+ * Return the request message associated with this operation.
+ *
+ * {@inheritDoc}
+ *
+ * @throws ControlLoopException if it occurs
+ */
+ @Override
+ public Object getRequest() throws ControlLoopException {
+ AppcLcmCommonHeader commonHeader = new AppcLcmCommonHeader();
+ commonHeader.setRequestId(onset.getRequestId());
+ commonHeader.setOriginatorId("POLICY");
+ commonHeader.setSubRequestId(String.valueOf(attempt));
+
+ // Policy will send a default ttl of 10 minutes (600 seconds)
+ Map<String, String> flags = new HashMap<>();
+ flags.put("ttl", "600");
+ commonHeader.setFlags(flags);
+
+ String action = null;
+ for (String recipe: recipes) {
+ if (recipe.equalsIgnoreCase(policy.getRecipe())) {
+ action = recipe;
+ break;
+ }
+ }
+
+ if (action == null) {
+ setErrorStatus("Error - invalid recipe");
+ }
+
+ Map<String, String> actionIdentifiers = new HashMap<>();
+
+ // The vnf-id is needed for both VNF and VM level operations
+ if (onset.getAai().containsKey(DCAE_VNF_NAME_FIELD)) {
+ actionIdentifiers.put("vnf-id", onset.getAai().get(DCAE_VNF_ID_FIELD));
+ } else {
+ logger.error("Error - no AAI DCAE VNF NAME key in the onset");
+ setErrorStatus("Error - no VNF NAME key in the onset");
+ }
+
+ if (this.policy.getTarget().getType() == TargetType.VM) {
+ if (onset.getAai().containsKey(DCAE_VSERVER_ID_FIELD)) {
+ actionIdentifiers.put("vserver-id", onset.getAai().get(DCAE_VSERVER_ID_FIELD));
+ } else {
+ logger.error("Error - no DCAE VSERVER ID key in the onset AAI\n");
+ setErrorStatus("Error - no VSERVER ID key in the onset");
+ }
+ }
+
+ String payload = setPayload(onset.getAai(), action.toLowerCase());
+
+ // construct an APPC LCM 'Request' message
+ AppcLcmInput request = new AppcLcmInput();
+
+ request.setCommonHeader(commonHeader);
+ request.setAction(action);
+ request.setActionIdentifiers(actionIdentifiers);
+ request.setPayload(payload);
+
+ // Pass the LCM request to the LCM wrapper
+ AppcLcmDmaapWrapper dmaapWrapper = new AppcLcmDmaapWrapper();
+ dmaapWrapper.setVersion("2.0");
+ AppcLcmBody appcBody = new AppcLcmBody();
+ appcBody.setInput(request);
+ dmaapWrapper.setBody(appcBody);
+ dmaapWrapper.setCorrelationId(onset.getRequestId() + "-" + attempt);
+ dmaapWrapper.setRpcName(toRpcName(action));
+ dmaapWrapper.setType("request");
+
+ // go to the LCM_PENDING state, under the assumption that the
+ // calling Drools code will send out the message we are returning
+ this.state = LCM_PENDING;
+ transaction.modify();
+ return dmaapWrapper;
+ }
+
+ /**
+ * This method is called by 'incomingMessage' when the message is a
+ * 'PolicyGuardResponse' message (leaving 'incomingMessage' to focus on
+ * 'Response' messages).
+ *
+ * @param response the received guard response message
+ */
+ void incomingGuardMessage(PolicyGuardResponse response) {
+ // this message is only meaningful if we are waiting for a
+ // 'guard' response -- ignore it, if this isn't the case
+ if (this.state == LCM_GUARD_PENDING) {
+ if ("Deny".equals(response.getResult())) {
+ // this is a guard failure
+ logger.error("LCM operation denied by 'Guard'");
+ this.message = "Denied by Guard";
+ this.result = PolicyResult.FAILURE_GUARD;
+ this.state = LCM_COMPLETE;
+ } else {
+ // everything else is treated as 'Permit'
+ this.state = LCM_BEGIN;
+ transaction.modify();
+ }
+ }
+ }
+
+ /**
+ * An incoming message is being delivered to the operation.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void incomingMessage(Object object) {
+ if (! (object instanceof AppcLcmDmaapWrapper)) {
+ if (object instanceof PolicyGuardResponse) {
+ incomingGuardMessage((PolicyGuardResponse)object);
+ return;
+ } else if (object instanceof ControlLoopEvent) {
+ incomingAbatedEvent((ControlLoopEvent) object);
+ return;
+ }
+ // ignore this message (not sure why we even got it)
+ return;
+ }
+
+ // If we reach this point, we have a 'AppcLcmDmaapWrapper' instance.
+ // The rest of this method is mostly copied from
+ // 'ControlLoopOperationManager.onResponse'.
+
+ AppcLcmOutput response = ((AppcLcmDmaapWrapper)object).getBody().getOutput();
+
+ //
+ // Determine which subrequestID (ie. attempt)
+ //
+ int operationAttempt;
+ try {
+ operationAttempt = Integer.parseInt(response.getCommonHeader()
+ .getSubRequestId());
+ } catch (NumberFormatException e) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ // If the attempt cannot be parsed then we assume it is
+ // the current attempt
+ //
+ this.completeOperation(this.attempt, "Policy was unable to parse APP-C SubRequestID (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Sanity check the response message
+ //
+ if (response.getStatus() == null) {
+ //
+ // We cannot tell what happened if this doesn't exist
+ //
+ this.completeOperation(operationAttempt,
+ "Policy was unable to parse APP-C response status field (it was null).",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Get the Response Code
+ //
+ AppcLcmResponseCode responseValue = AppcLcmResponseCode.toResponseValue(response.getStatus().getCode());
+ if (responseValue == null) {
+ //
+ // We are unaware of this code
+ //
+ this.completeOperation(operationAttempt, "Policy was unable to parse APP-C response status code field.",
+ PolicyResult.FAILURE_EXCEPTION);
+ return;
+ }
+ //
+ // Ok, let's figure out what APP-C's response is
+ //
+ switch (responseValue) {
+ case ACCEPTED:
+ //
+ // This is good, they got our original message and
+ // acknowledged it.
+ //
+ // Is there any need to track this?
+ //
+ return;
+ case PARTIAL_SUCCESS:
+ //
+ // Keep count of partial successes to determine
+ // if retries should be done at the vnf level
+ //
+ this.partialSuccessCount++;
+ return;
+ case PARTIAL_FAILURE:
+ //
+ // Keep count of partial failures to determine
+ // if no retries should be done
+ //
+ this.partialFailureCount++;
+ return;
+ case ERROR:
+ case REJECT:
+ //
+ // We'll consider these two codes as exceptions
+ //
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.FAILURE_EXCEPTION);
+ return;
+ case SUCCESS:
+ //
+ //
+ //
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.SUCCESS);
+ return;
+ case FAILURE:
+ // For the VNF level operations, retries will be attempted only
+ // if ALL individual VMs failed the operation
+ if (this.partialSuccessCount == 0) {
+ // Since there are no partial successes, that means all VMs failed
+ // if all vms fail, we can retry the VNF level action
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.FAILURE);
+ } else if (this.partialFailureCount > 0) {
+ // Since only a subset of VMs had partial failures,
+ // the result should go to final failure and not
+ // retry or move on to the next policy in the chain.
+ this.completeOperation(operationAttempt, response.getStatus()
+ .getMessage(), PolicyResult.FAILURE_EXCEPTION);
+ }
+ return;
+ default:
+ break;
+ }
+ }
+
+ /**
+ * This method is called by 'incomingMessage' only when an 'ABATED' event is received before an APPC
+ * request is sent.
+ *
+ * @param event the control loop event that was received
+ */
+ private void incomingAbatedEvent(ControlLoopEvent event) {
+ // check if ClosedLoopEventStatus is 'abated'
+ if (event.isEventStatusValid() && "ABATED".equalsIgnoreCase(event.getClosedLoopEventStatus().toString())) {
+ this.result = PolicyResult.SUCCESS;
+ this.message = "Abatement received before APPC request was sent";
+ this.state = LCM_COMPLETE;
+ }
+ }
+
+ /**
+ * This method is called by 'incomingMessage' in order to complete the
+ * operation.
+ *
+ * @param attempt the operation attempt indicated in the response message
+ * @param message the value to store in the 'message' field'
+ * @param result the value to store in the 'result' field
+ */
+ void completeOperation(int attempt, String message, PolicyResult result) {
+ logger.debug("LCM: completeOperation("
+ + "this.attempt=" + this.attempt
+ + ", attempt=" + attempt
+ + ", result=" + result
+ + ", message=" + message);
+ if (this.attempt == attempt) {
+ // we need to verify that the attempt matches in order to reduce the
+ // chances that we are reacting to a prior 'Response' message that
+ // was received after we timed out (unfortunately, we can't guarantee
+ // this, because we have no reliable way to verify the 'recipe')
+
+ this.message = message;
+ this.result = result;
+ state = LCM_COMPLETE;
+ }
+ }
+
+ /**
+ * The operation has timed out.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void timeout() {
+ result = PolicyResult.FAILURE_TIMEOUT;
+ state = LCM_COMPLETE;
+ }
+
+ void setErrorStatus(String message) throws ControlLoopException {
+ result = PolicyResult.FAILURE_EXCEPTION;
+ state = LCM_ERROR;
+ this.message = message;
+ transaction.modify();
+ throw new ControlLoopException(message);
+ }
+
+ /**
+ * This is called right after it's history entry has been completed.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public void histEntryCompleted(ControlLoopOperation histEntry) {
+ // give 'guard' a chance to create a DB entry (this only happens if
+ // we really have a 'GuardContext', and all of the needed parameters
+ // were provided in the '*-controller.properties' file)
+ guardAdjunct.asyncCreateDbEntry(histEntry, target);
+ }
+}
diff --git a/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java
new file mode 100644
index 000000000..ca812a4db
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/java/org/onap/policy/m2/appclcm/model/AppcLcmResponseCode.java
@@ -0,0 +1,58 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * m2/appclcm
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.m2.appclcm.model;
+
+import java.io.Serializable;
+
+public enum AppcLcmResponseCode implements Serializable {
+ ACCEPTED, ERROR, REJECT, SUCCESS, FAILURE, PARTIAL_SUCCESS, PARTIAL_FAILURE;
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Translates the code to a string value that represents the meaning of the
+ * code.
+ *
+ * @param code
+ * the numeric value that is returned by APPC based on success,
+ * failure, etc. of the action requested
+ * @return the enum value equivalent of the APPC response code
+ */
+ public static AppcLcmResponseCode toResponseValue(int code) {
+ if (code == 100) {
+ return ACCEPTED;
+ } else if (code == 200) {
+ return ERROR;
+ } else if (code >= 300 && code <= 316) {
+ return REJECT;
+ } else if (code == 400) {
+ return SUCCESS;
+ } else if (code == 450 || (code >= 401 && code <= 406)) {
+ return FAILURE;
+ } else if (code == 500) {
+ return PARTIAL_SUCCESS;
+ } else if (code >= 501 && code <= 599) {
+ return PARTIAL_FAILURE;
+ }
+ return null;
+ }
+
+}
diff --git a/controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor b/controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor
new file mode 100644
index 000000000..2e6065608
--- /dev/null
+++ b/controlloop/m2/appclcm/src/main/resources/META-INF/services/org.onap.policy.m2.base.Actor
@@ -0,0 +1 @@
+org.onap.policy.m2.appclcm.AppcLcmActor