2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.policy.drools.apps.controller.usecases;
23 import static org.onap.policy.controlloop.ControlLoopTargetType.PNF;
24 import static org.onap.policy.controlloop.ControlLoopTargetType.VM;
25 import static org.onap.policy.controlloop.ControlLoopTargetType.VNF;
26 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.GENERIC_VNF_IS_CLOSED_LOOP_DISABLED;
27 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.GENERIC_VNF_PROV_STATUS;
28 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.GENERIC_VNF_VNF_ID;
29 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.GENERIC_VNF_VNF_NAME;
30 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.PNF_IS_IN_MAINT;
31 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.PNF_NAME;
32 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.PROV_STATUS_ACTIVE;
33 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.VM_NAME;
34 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.VNF_NAME;
35 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.VSERVER_IS_CLOSED_LOOP_DISABLED;
36 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.VSERVER_PROV_STATUS;
37 import static org.onap.policy.drools.apps.controller.usecases.UsecasesConstants.VSERVER_VSERVER_NAME;
39 import java.util.ArrayDeque;
40 import java.util.Deque;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedList;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
48 import lombok.NonNull;
50 import lombok.ToString;
51 import org.apache.commons.lang3.StringUtils;
52 import org.drools.core.WorkingMemory;
53 import org.kie.api.runtime.rule.FactHandle;
54 import org.onap.policy.controlloop.ControlLoopEventStatus;
55 import org.onap.policy.controlloop.ControlLoopException;
56 import org.onap.policy.controlloop.ControlLoopNotificationType;
57 import org.onap.policy.controlloop.ControlLoopOperation;
58 import org.onap.policy.controlloop.ControlLoopResponse;
59 import org.onap.policy.controlloop.VirtualControlLoopEvent;
60 import org.onap.policy.controlloop.VirtualControlLoopNotification;
61 import org.onap.policy.controlloop.actorserviceprovider.OperationFinalResult;
62 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
63 import org.onap.policy.controlloop.actorserviceprovider.OperationProperties;
64 import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
65 import org.onap.policy.controlloop.actorserviceprovider.TargetType;
66 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
67 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
68 import org.onap.policy.controlloop.eventmanager.ActorConstants;
69 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager;
70 import org.onap.policy.controlloop.eventmanager.StepContext;
71 import org.onap.policy.drools.apps.controller.usecases.step.AaiCqStep2;
72 import org.onap.policy.drools.apps.controller.usecases.step.AaiGetPnfStep2;
73 import org.onap.policy.drools.apps.controller.usecases.step.AaiGetTenantStep2;
74 import org.onap.policy.drools.apps.controller.usecases.step.GetTargetEntityStep2;
75 import org.onap.policy.drools.apps.controller.usecases.step.GuardStep2;
76 import org.onap.policy.drools.apps.controller.usecases.step.LockStep2;
77 import org.onap.policy.drools.apps.controller.usecases.step.Step2;
78 import org.onap.policy.drools.domain.models.operational.ActorOperation;
79 import org.onap.policy.drools.domain.models.operational.Operation;
80 import org.onap.policy.drools.domain.models.operational.OperationalTarget;
81 import org.onap.policy.drools.system.PolicyEngine;
82 import org.onap.policy.drools.system.PolicyEngineConstants;
83 import org.onap.policy.sdnr.PciMessage;
84 import org.slf4j.Logger;
85 import org.slf4j.LoggerFactory;
88 * Manager for a single control loop event. Once this has been created, the event can be
89 * retracted from working memory. Processing progresses through each policy, which
90 * involves at least one step. As a step is processed, additional preprocessor steps may
91 * be pushed onto the queue (e.g., locks, A&AI queries, guards).
93 @ToString(onlyExplicitlyIncluded = true)
94 public class UsecasesEventManager extends ControlLoopEventManager implements StepContext {
96 private static final Logger logger = LoggerFactory.getLogger(UsecasesEventManager.class);
97 private static final long serialVersionUID = -1216568161322872641L;
100 * Maximum number of steps, for a single policy, allowed in the queue at a time. This
101 * prevents an infinite loop occurring with calls to {@link #loadPreprocessorSteps()}.
103 public static final int MAX_STEPS = 30;
106 * If there's a failure from one of these actors, then the TOSCA processing should be
109 private static final Set<String> ABORT_ACTORS = Set.of(ActorConstants.CL_TIMEOUT_ACTOR, ActorConstants.LOCK_ACTOR);
111 private static final Set<String> VALID_TARGETS = Stream
112 .of(VM_NAME, VNF_NAME, VSERVER_VSERVER_NAME, GENERIC_VNF_VNF_ID, GENERIC_VNF_VNF_NAME, PNF_NAME)
113 .map(String::toLowerCase).collect(Collectors.toSet());
115 private static final Set<String> TRUE_VALUES = Set.of("true", "t", "yes", "y");
118 * Names of Operation properties for which A&AI PNF query is needed.
120 private static final Set<String> PNF_PROPERTIES = Set.of(OperationProperties.AAI_PNF);
123 * Names of Operation properties for which A&AI Tenant query is needed.
125 private static final Set<String> TENANT_PROPERTIES = Set.of(OperationProperties.AAI_VSERVER_LINK);
128 * Names of Operation properties for which A&AI custom query is needed.
130 private static final Set<String> CQ_PROPERTIES = Set.of(OperationProperties.AAI_DEFAULT_CLOUD_REGION,
131 OperationProperties.AAI_VNF, OperationProperties.AAI_SERVICE_MODEL,
132 OperationProperties.AAI_VNF_MODEL, OperationProperties.AAI_SERVICE,
133 OperationProperties.AAI_RESOURCE_VNF, UsecasesConstants.AAI_DEFAULT_GENERIC_VNF);
136 LOAD_POLICY, POLICY_LOADED, AWAITING_OUTCOME, DONE
139 public enum NewEventStatus {
140 FIRST_ONSET, SUBSEQUENT_ONSET, FIRST_ABATEMENT, SUBSEQUENT_ABATEMENT, SYNTAX_ERROR
144 private final VirtualControlLoopEvent event;
147 * Request ID, as a String.
149 private final String requestIdStr;
156 * {@code True} if the event has been accepted (i.e., an "ACTIVE" notification has
157 * been delivered), {@code false} otherwise.
161 private boolean accepted;
164 * Queue of steps waiting to be performed.
167 private final transient Deque<Step2> steps = new ArrayDeque<>(6);
170 * Number of attempts, so far, for the current step.
173 private int attempts;
176 * Policy currently being processed.
178 private Operation policy;
181 * Result of the last policy operation. This is just a place where the rules can store
182 * the value for passing to {@link #loadNextPolicy()}.
186 private OperationResult result = OperationResult.SUCCESS;
189 private int numOnsets = 1;
191 private int numAbatements = 0;
193 private VirtualControlLoopEvent abatement = null;
196 * Full history of operations that have been processed by the rules. This includes the
197 * items in {@link #partialHistory}.
200 private final transient Deque<OperationOutcome2> fullHistory = new LinkedList<>();
203 * History of operations that have been processed by the rules for the current policy.
204 * When a step is started, its "start" outcome is added. However, once it completes,
205 * its "start" outcome is removed and the "completed" outcome is added.
208 private final transient Deque<OperationOutcome2> partialHistory = new LinkedList<>();
211 private OperationFinalResult finalResult = null;
214 * Message to be placed into the final notification. Typically used when something
215 * causes processing to abort.
218 private String finalMessage = null;
220 private final transient WorkingMemory workMem;
221 private transient FactHandle factHandle;
225 * Constructs the object.
227 * @param params control loop parameters
228 * @param event event to be managed by this object
229 * @param workMem working memory to update if this changes
230 * @throws ControlLoopException if the event is invalid or if a YAML processor cannot
233 public UsecasesEventManager(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem)
234 throws ControlLoopException {
236 super(params, event.getRequestId());
238 checkEventSyntax(event);
240 if (isClosedLoopDisabled(event)) {
241 throw new IllegalStateException("is-closed-loop-disabled is set to true on VServer or VNF");
244 if (isProvStatusInactive(event)) {
245 throw new IllegalStateException("prov-status is not ACTIVE on VServer or VNF");
249 this.workMem = workMem;
250 this.requestIdStr = getRequestId().toString();
254 public void destroy() {
255 for (Step2 step : steps) {
263 * Starts the manager and loads the first policy.
265 * @throws ControlLoopException if the processor cannot get a policy
267 public void start() throws ControlLoopException {
269 throw new IllegalStateException("manager is no longer active");
272 if ((factHandle = workMem.getFactHandle(this)) == null) {
273 throw new IllegalStateException("manager is not in working memory");
276 if (!steps.isEmpty()) {
277 throw new IllegalStateException("manager already started");
284 * Indicates that processing has been aborted.
286 * @param finalState final state
287 * @param finalResult final result
288 * @param finalMessage final message
290 public void abort(@NonNull State finalState, OperationFinalResult finalResult, String finalMessage) {
291 this.state = finalState;
292 this.finalResult = finalResult;
293 this.finalMessage = finalMessage;
297 * Loads the next policy.
299 * @param lastResult result from the last policy
301 * @throws ControlLoopException if the processor cannot get a policy
303 public void loadNextPolicy(@NonNull OperationResult lastResult) throws ControlLoopException {
304 getProcessor().nextPolicyForResult(lastResult);
309 * Loads the current policy.
311 * @throws ControlLoopException if the processor cannot get a policy
313 private void loadPolicy() throws ControlLoopException {
314 partialHistory.clear();
316 if ((finalResult = getProcessor().checkIsCurrentPolicyFinal()) != null) {
317 // final policy - nothing more to do
321 policy = getProcessor().getCurrentPolicy();
323 ActorOperation actor = policy.getActorOperation();
325 OperationalTarget target = actor.getTarget();
326 String targetType = (target != null ? target.getTargetType() : null);
327 Map<String, String> entityIds = (target != null ? target.getEntityIds() : null);
329 // convert policy payload from Map<String,String> to Map<String,Object>
330 Map<String, Object> payload = new LinkedHashMap<>();
331 if (actor.getPayload() != null) {
332 payload.putAll(actor.getPayload());
336 ControlLoopOperationParams params = ControlLoopOperationParams.builder()
337 .actorService(getActorService())
338 .actor(actor.getActor())
339 .operation(actor.getOperation())
340 .requestId(event.getRequestId())
342 .executor(getExecutor())
343 .retry(policy.getRetries())
344 .timeoutSec(policy.getTimeout())
345 .targetType(TargetType.toTargetType(targetType))
346 .targetEntityIds(entityIds)
348 .startCallback(this::onStart)
349 .completeCallback(this::onComplete)
353 // load the policy's operation
354 steps.add(new Step2(this, params, event));
358 * Loads the preprocessor steps needed by the step that's at the front of the queue.
360 public void loadPreprocessorSteps() {
361 if (steps.size() >= MAX_STEPS) {
362 throw new IllegalStateException("too many steps");
365 final Step2 step = steps.peek();
367 // initialize the step so we can query its properties
370 // determine if any A&AI queries are needed
371 boolean needCq = false;
372 boolean needPnf = false;
373 boolean needTenant = false;
374 boolean needTargetEntity = false;
376 for (String propName : step.getPropertyNames()) {
377 needCq = needCq || CQ_PROPERTIES.contains(propName);
378 needPnf = needPnf || PNF_PROPERTIES.contains(propName);
379 needTenant = needTenant || TENANT_PROPERTIES.contains(propName);
380 needTargetEntity = needTargetEntity || OperationProperties.AAI_TARGET_ENTITY.equals(propName);
384 * The Policy's actual operation requires additional, implicit steps, such as
385 * locking and guards.
387 final boolean needPolicySteps = step.isPolicyStep();
391 * NOTE: need to push steps onto the queue in the OPPOSITE order in which they
396 // GUARD must be pushed first
397 if (needPolicySteps) {
398 steps.push(new GuardStep2(step, getClosedLoopControlName()));
403 steps.push(new AaiCqStep2(step));
407 steps.push(new AaiGetPnfStep2(step));
411 steps.push(new AaiGetTenantStep2(step));
414 // LOCK must be pushed after the queries
415 if (needPolicySteps) {
416 steps.push(new LockStep2(step));
419 // GET-TARGET-ENTITY should be pushed last
420 if (needTargetEntity) {
421 steps.push(new GetTargetEntityStep2(step));
426 * Executes the first step in the queue.
428 * @return {@code true} if the step was started, {@code false} if it is no longer
429 * needed (or if the queue is empty)
431 public boolean executeStep() {
434 Step2 step = steps.peek();
439 return step.start(getEndTimeMs() - System.currentTimeMillis());
443 * Discards the current step, if any.
445 public void nextStep() {
450 * Increments the number of attempts.
452 public void bumpAttempts() {
457 * Determines if the TOSCA should be aborted due to the given outcome.
459 * @param outcome outcome to examine
460 * @return {@code true} if the TOSCA should be aborted, {@code false} otherwise
462 public boolean isAbort(OperationOutcome outcome) {
463 return (outcome.getResult() != OperationResult.SUCCESS && ABORT_ACTORS.contains(outcome.getActor()));
467 * Adds the outcome to the history.
469 * @param outcome outcome to add
471 public void addToHistory(OperationOutcome outcome) {
472 OperationOutcome2 last = partialHistory.peekLast();
474 if (last != null && last.getOutcome().getEnd() == null
475 && last.getOutcome().isFor(outcome.getActor(), outcome.getOperation())) {
476 // last item was a "start" - remove it
477 partialHistory.removeLast();
479 if (fullHistory.peekLast() == last) {
480 fullHistory.removeLast();
484 OperationOutcome2 outcome2 = new OperationOutcome2(outcome);
485 partialHistory.add(outcome2);
486 fullHistory.add(outcome2);
490 * Makes a notification message for the current operation.
492 * @return a new notification
494 public VirtualControlLoopNotification makeNotification() {
495 VirtualControlLoopNotification notif = new VirtualControlLoopNotification(event);
496 notif.setNotification(ControlLoopNotificationType.OPERATION);
497 notif.setFrom("policy");
498 notif.setPolicyVersion(getPolicyVersion());
500 if (finalResult != null) {
504 OperationOutcome2 last = partialHistory.peekLast();
509 notif.setMessage(last.getClOperation().toHistory());
510 notif.setHistory(partialHistory.stream().map(OperationOutcome2::getClOperation).collect(Collectors.toList()));
516 * Delivers a notification to a topic.
518 * @param sinkName name of the topic sink
519 * @param notification notification to be published, or {@code null} if nothing is to
521 * @param notificationType type of notification, used when logging error messages
522 * @param ruleName name of the rule doing the publishing
524 public <T> void deliver(String sinkName, T notification, String notificationType, String ruleName) {
526 if (notification != null) {
527 getPolicyEngineManager().deliver(sinkName, notification);
530 } catch (RuntimeException e) {
531 logger.warn("{}: {}.{}: manager={} exception publishing {}", getClosedLoopControlName(), getPolicyName(),
532 ruleName, this, notificationType, e);
537 * Get the last operation, as a message.
539 * @return the last operation, as a message
541 public String getOperationMessage() {
542 OperationOutcome2 last = fullHistory.peekLast();
543 return (last == null ? null : last.getClOperation().toMessage());
547 * Stores an operation outcome in the DB.
549 * @param outcome operation outcome to store
551 public void storeInDataBase(OperationOutcome2 outcome) {
552 String targetEntity = getProperty(OperationProperties.AAI_TARGET_ENTITY);
554 getDataManager().store(requestIdStr, event, targetEntity, outcome.getClOperation());
558 * Makes a control loop response.
560 * @param outcome operation outcome
561 * @return a new control loop response, or {@code null} if none is required
563 public ControlLoopResponse makeControlLoopResponse(OperationOutcome outcome) {
564 ControlLoopResponse clRsp = new ControlLoopResponse();
565 clRsp.setFrom(outcome.getActor());
566 clRsp.setTarget("DCAE");
567 clRsp.setClosedLoopControlName(event.getClosedLoopControlName());
568 clRsp.setPolicyName(event.getPolicyName());
569 clRsp.setPolicyVersion(event.getPolicyVersion());
570 clRsp.setRequestId(event.getRequestId());
571 clRsp.setVersion(event.getVersion());
573 Object obj = outcome.getResponse();
574 if (!(obj instanceof PciMessage)) {
578 PciMessage msg = (PciMessage) obj;
579 if (msg.getBody() != null && msg.getBody().getOutput() != null) {
580 clRsp.setPayload(msg.getBody().getOutput().getPayload());
587 * An event onset/abatement.
589 * @param newEvent the event
592 public NewEventStatus onNewEvent(VirtualControlLoopEvent newEvent) {
594 checkEventSyntax(newEvent);
596 if (newEvent.getClosedLoopEventStatus() == ControlLoopEventStatus.ONSET) {
597 if (newEvent.equals(event)) {
598 return NewEventStatus.FIRST_ONSET;
602 return NewEventStatus.SUBSEQUENT_ONSET;
605 if (abatement == null) {
606 abatement = newEvent;
608 return NewEventStatus.FIRST_ABATEMENT;
611 return NewEventStatus.SUBSEQUENT_ABATEMENT;
614 } catch (ControlLoopException e) {
615 logger.error("{}: onNewEvent threw an exception", this, e);
616 return NewEventStatus.SYNTAX_ERROR;
621 * Check an event syntax.
623 * @param event the event syntax
624 * @throws ControlLoopException if an error occurs
626 protected void checkEventSyntax(VirtualControlLoopEvent event) throws ControlLoopException {
627 validateStatus(event);
628 if (StringUtils.isBlank(event.getClosedLoopControlName())) {
629 throw new ControlLoopException("No control loop name");
631 if (event.getRequestId() == null) {
632 throw new ControlLoopException("No request ID");
634 if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ABATED) {
637 if (StringUtils.isBlank(event.getTarget())) {
638 throw new ControlLoopException("No target field");
639 } else if (!VALID_TARGETS.contains(event.getTarget().toLowerCase())) {
640 throw new ControlLoopException("target field invalid");
642 validateAaiData(event);
645 private void validateStatus(VirtualControlLoopEvent event) throws ControlLoopException {
646 if (event.getClosedLoopEventStatus() != ControlLoopEventStatus.ONSET
647 && event.getClosedLoopEventStatus() != ControlLoopEventStatus.ABATED) {
648 throw new ControlLoopException("Invalid value in closedLoopEventStatus");
652 private void validateAaiData(VirtualControlLoopEvent event) throws ControlLoopException {
653 Map<String, String> eventAai = event.getAai();
654 if (eventAai == null) {
655 throw new ControlLoopException("AAI is null");
657 if (event.getTargetType() == null) {
658 throw new ControlLoopException("The Target type is null");
660 switch (event.getTargetType()) {
663 validateAaiVmVnfData(eventAai);
666 validateAaiPnfData(eventAai);
669 throw new ControlLoopException("The target type is not supported");
673 private void validateAaiVmVnfData(Map<String, String> eventAai) throws ControlLoopException {
674 if (eventAai.get(GENERIC_VNF_VNF_ID) == null && eventAai.get(VSERVER_VSERVER_NAME) == null
675 && eventAai.get(GENERIC_VNF_VNF_NAME) == null) {
676 throw new ControlLoopException(
677 "generic-vnf.vnf-id or generic-vnf.vnf-name or vserver.vserver-name information missing");
681 private void validateAaiPnfData(Map<String, String> eventAai) throws ControlLoopException {
682 if (eventAai.get(PNF_NAME) == null) {
683 throw new ControlLoopException("AAI PNF object key pnf-name is missing");
688 * Is closed loop disabled for an event.
690 * @param event the event
691 * @return <code>true</code> if the control loop is disabled, <code>false</code>
694 private static boolean isClosedLoopDisabled(VirtualControlLoopEvent event) {
695 Map<String, String> aai = event.getAai();
696 return (isAaiTrue(aai.get(VSERVER_IS_CLOSED_LOOP_DISABLED))
697 || isAaiTrue(aai.get(GENERIC_VNF_IS_CLOSED_LOOP_DISABLED))
698 || isAaiTrue(aai.get(PNF_IS_IN_MAINT)));
702 * Does provisioning status, for an event, have a value other than ACTIVE.
704 * @param event the event
705 * @return {@code true} if the provisioning status is neither ACTIVE nor {@code null},
706 * {@code false} otherwise
708 private static boolean isProvStatusInactive(VirtualControlLoopEvent event) {
709 Map<String, String> aai = event.getAai();
710 return !(PROV_STATUS_ACTIVE.equalsIgnoreCase(aai.getOrDefault(VSERVER_PROV_STATUS, PROV_STATUS_ACTIVE))
711 && PROV_STATUS_ACTIVE.equalsIgnoreCase(
712 aai.getOrDefault(GENERIC_VNF_PROV_STATUS, PROV_STATUS_ACTIVE)));
716 * Determines the boolean value represented by the given AAI field value.
718 * @param aaiValue value to be examined
719 * @return the boolean value represented by the field value, or {@code false} if the
720 * value is {@code null}
722 private static boolean isAaiTrue(String aaiValue) {
723 return (aaiValue != null && TRUE_VALUES.contains(aaiValue.toLowerCase()));
727 public void onStart(OperationOutcome outcome) {
728 super.onStart(outcome);
729 workMem.update(factHandle, this);
733 public void onComplete(OperationOutcome outcome) {
734 super.onComplete(outcome);
735 workMem.update(factHandle, this);
740 public class OperationOutcome2 {
741 private final int attempt;
742 private final OperationOutcome outcome;
743 private final ControlLoopOperation clOperation;
746 * Constructs the object.
748 * @param outcome outcome of the operation
750 public OperationOutcome2(OperationOutcome outcome) {
751 this.outcome = outcome;
752 this.attempt = attempts;
754 clOperation = outcome.toControlLoopOperation();
757 OperationalTarget target = policy.getActorOperation().getTarget();
758 String targetStr = (target != null ? target.toString() : null);
759 clOperation.setTarget(targetStr);
761 if (outcome.getEnd() == null) {
762 clOperation.setOutcome("Started");
763 } else if (clOperation.getOutcome() == null) {
764 clOperation.setOutcome("");
769 // these following methods may be overridden by junit tests
771 protected PolicyEngine getPolicyEngineManager() {
772 return PolicyEngineConstants.getManager();