diff options
author | Straubs, Ralph (rs8887) <rs8887@att.com> | 2019-11-19 04:11:23 -0600 |
---|---|---|
committer | Straubs, Ralph (rs8887) <rs8887@att.com> | 2020-01-10 03:20:23 -0600 |
commit | 3e05cb41202145e113853392e9837abf3f6ec12c (patch) | |
tree | 0c504018436c3933f563caa37c3ea0512c82181e /controlloop/m2/appclcm/src/main | |
parent | 927c7c177670a812a4a4139281ef84e85b520645 (diff) |
Add m2 model, including the LCM application
Issue-ID: POLICY-1948
Change-Id: I18a5231d3102073c928a591c9e91b241b7093680
Signed-off-by: Straubs, Ralph (rs8887) <rs8887@att.com>
Diffstat (limited to 'controlloop/m2/appclcm/src/main')
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 |