cf8cc8ee33ee97c9f44ad2583247affd06dcf3fe
[policy/drools-applications.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.policy.drools.apps.controller.usecases;
22
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;
38
39 import java.util.ArrayDeque;
40 import java.util.Deque;
41 import java.util.LinkedHashMap;
42 import java.util.LinkedList;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47 import lombok.Getter;
48 import lombok.NonNull;
49 import lombok.Setter;
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.OperationOutcome;
62 import org.onap.policy.controlloop.actorserviceprovider.OperationProperties;
63 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
64 import org.onap.policy.controlloop.drl.legacy.ControlLoopParams;
65 import org.onap.policy.controlloop.eventmanager.ActorConstants;
66 import org.onap.policy.controlloop.eventmanager.ControlLoopEventManager;
67 import org.onap.policy.controlloop.eventmanager.StepContext;
68 import org.onap.policy.controlloop.policy.FinalResult;
69 import org.onap.policy.controlloop.policy.Policy;
70 import org.onap.policy.controlloop.policy.PolicyResult;
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.system.PolicyEngine;
79 import org.onap.policy.drools.system.PolicyEngineConstants;
80 import org.onap.policy.sdnr.PciMessage;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83
84 /**
85  * Manager for a single control loop event. Once this has been created, the event can be
86  * retracted from working memory. Processing progresses through each policy, which
87  * involves at least one step. As a step is processed, additional preprocessor steps may
88  * be pushed onto the queue (e.g., locks, A&AI queries, guards).
89  */
90 @ToString(onlyExplicitlyIncluded = true)
91 public class UsecasesEventManager extends ControlLoopEventManager implements StepContext {
92
93     private static final Logger logger = LoggerFactory.getLogger(UsecasesEventManager.class);
94     private static final long serialVersionUID = -1216568161322872641L;
95
96     /**
97      * Maximum number of steps, for a single policy, allowed in the queue at a time. This
98      * prevents an infinite loop occurring with calls to {@link #loadPreprocessorSteps()}.
99      */
100     public static final int MAX_STEPS = 30;
101
102     /**
103      * If there's a failure from one of these actors, then the TOSCA processing should be
104      * aborted.
105      */
106     private static final Set<String> ABORT_ACTORS = Set.of(ActorConstants.CL_TIMEOUT_ACTOR, ActorConstants.LOCK_ACTOR);
107
108     private static final Set<String> VALID_TARGETS = Stream
109                     .of(VM_NAME, VNF_NAME, VSERVER_VSERVER_NAME, GENERIC_VNF_VNF_ID, GENERIC_VNF_VNF_NAME, PNF_NAME)
110                     .map(String::toLowerCase).collect(Collectors.toSet());
111
112     private static final Set<String> TRUE_VALUES = Set.of("true", "t", "yes", "y");
113
114     /**
115      * Names of Operation properties for which A&AI PNF query is needed.
116      */
117     private static final Set<String> PNF_PROPERTIES = Set.of(OperationProperties.AAI_PNF);
118
119     /**
120      * Names of Operation properties for which A&AI Tenant query is needed.
121      */
122     private static final Set<String> TENANT_PROPERTIES = Set.of(OperationProperties.AAI_VSERVER_LINK);
123
124     /**
125      * Names of Operation properties for which A&AI custom query is needed.
126      */
127     private static final Set<String> CQ_PROPERTIES = Set.of(OperationProperties.AAI_DEFAULT_CLOUD_REGION,
128                     OperationProperties.AAI_VNF, OperationProperties.AAI_SERVICE_MODEL,
129                     OperationProperties.AAI_VNF_MODEL, OperationProperties.AAI_SERVICE,
130                     OperationProperties.AAI_RESOURCE_VNF, UsecasesConstants.AAI_DEFAULT_GENERIC_VNF);
131
132     public enum State {
133         LOAD_POLICY, POLICY_LOADED, AWAITING_OUTCOME, DONE
134     }
135
136     public enum NewEventStatus {
137         FIRST_ONSET, SUBSEQUENT_ONSET, FIRST_ABATEMENT, SUBSEQUENT_ABATEMENT, SYNTAX_ERROR
138     }
139
140     @Getter
141     private final VirtualControlLoopEvent event;
142
143     /**
144      * Request ID, as a String.
145      */
146     private final String requestIdStr;
147
148     @Getter
149     @Setter
150     private State state;
151
152     /**
153      * {@code True} if the event has been accepted (i.e., an "ACTIVE" notification has
154      * been delivered), {@code false} otherwise.
155      */
156     @Getter
157     @Setter
158     private boolean accepted;
159
160     /**
161      * Queue of steps waiting to be performed.
162      */
163     @Getter
164     private final transient Deque<Step2> steps = new ArrayDeque<>(6);
165
166     /**
167      * Number of attempts, so far, for the current step.
168      */
169     @Getter
170     private int attempts;
171
172     /**
173      * Policy currently being processed.
174      */
175     private Policy policy;
176
177     /**
178      * Result of the last policy operation. This is just a place where the rules can store
179      * the value for passing to {@link #loadNextPolicy()}.
180      */
181     @Getter
182     @Setter
183     private PolicyResult result = PolicyResult.SUCCESS;
184
185     @ToString.Include
186     private int numOnsets = 1;
187     @ToString.Include
188     private int numAbatements = 0;
189
190     private VirtualControlLoopEvent abatement = null;
191
192     /**
193      * Full history of operations that have been processed by the rules. This includes the
194      * items in {@link #partialHistory}.
195      */
196     @Getter
197     private final transient Deque<OperationOutcome2> fullHistory = new LinkedList<>();
198
199     /**
200      * History of operations that have been processed by the rules for the current policy.
201      * When a step is started, its "start" outcome is added. However, once it completes,
202      * its "start" outcome is removed and the "completed" outcome is added.
203      */
204     @Getter
205     private final transient Deque<OperationOutcome2> partialHistory = new LinkedList<>();
206
207     @Getter
208     private FinalResult finalResult = null;
209
210     /**
211      * Message to be placed into the final notification. Typically used when something
212      * causes processing to abort.
213      */
214     @Getter
215     private String finalMessage = null;
216
217     private final transient WorkingMemory workMem;
218     private transient FactHandle factHandle;
219
220
221     /**
222      * Constructs the object.
223      *
224      * @param params control loop parameters
225      * @param event event to be managed by this object
226      * @param workMem working memory to update if this changes
227      * @throws ControlLoopException if the event is invalid or if a YAML processor cannot
228      *         be created
229      */
230     public UsecasesEventManager(ControlLoopParams params, VirtualControlLoopEvent event, WorkingMemory workMem)
231                     throws ControlLoopException {
232
233         super(params, event.getRequestId());
234
235         checkEventSyntax(event);
236
237         if (isClosedLoopDisabled(event)) {
238             throw new IllegalStateException("is-closed-loop-disabled is set to true on VServer or VNF");
239         }
240
241         if (isProvStatusInactive(event)) {
242             throw new IllegalStateException("prov-status is not ACTIVE on VServer or VNF");
243         }
244
245         this.event = event;
246         this.workMem = workMem;
247         this.requestIdStr = getRequestId().toString();
248     }
249
250     @Override
251     public void destroy() {
252         for (Step2 step : steps) {
253             step.cancel();
254         }
255
256         super.destroy();
257     }
258
259     /**
260      * Starts the manager and loads the first policy.
261      *
262      * @throws ControlLoopException if the processor cannot get a policy
263      */
264     public void start() throws ControlLoopException {
265         if (!isActive()) {
266             throw new IllegalStateException("manager is no longer active");
267         }
268
269         if ((factHandle = workMem.getFactHandle(this)) == null) {
270             throw new IllegalStateException("manager is not in working memory");
271         }
272
273         if (!steps.isEmpty()) {
274             throw new IllegalStateException("manager already started");
275         }
276
277         loadPolicy();
278     }
279
280     /**
281      * Indicates that processing has been aborted.
282      *
283      * @param finalState final state
284      * @param finalResult final result
285      * @param finalMessage final message
286      */
287     public void abort(@NonNull State finalState, FinalResult finalResult, String finalMessage) {
288         this.state = finalState;
289         this.finalResult = finalResult;
290         this.finalMessage = finalMessage;
291     }
292
293     /**
294      * Loads the next policy.
295      *
296      * @param lastResult result from the last policy
297      *
298      * @throws ControlLoopException if the processor cannot get a policy
299      */
300     public void loadNextPolicy(@NonNull PolicyResult lastResult) throws ControlLoopException {
301         getProcessor().nextPolicyForResult(lastResult);
302         loadPolicy();
303     }
304
305     /**
306      * Loads the current policy.
307      *
308      * @throws ControlLoopException if the processor cannot get a policy
309      */
310     private void loadPolicy() throws ControlLoopException {
311         partialHistory.clear();
312
313         if ((finalResult = getProcessor().checkIsCurrentPolicyFinal()) != null) {
314             // final policy - nothing more to do
315             return;
316         }
317
318         policy = getProcessor().getCurrentPolicy();
319
320         // convert policy payload from Map<String,String> to Map<String,Object>
321         Map<String, Object> payload = new LinkedHashMap<>();
322         if (policy.getPayload() != null) {
323             payload.putAll(policy.getPayload());
324         }
325
326         // @formatter:off
327         ControlLoopOperationParams params = ControlLoopOperationParams.builder()
328                         .actorService(getActorService())
329                         .actor(policy.getActor())
330                         .operation(policy.getRecipe())
331                         .requestId(event.getRequestId())
332                         .preprocessed(true)
333                         .executor(getExecutor())
334                         .target(policy.getTarget())
335                         .retry(policy.getRetry())
336                         .timeoutSec(policy.getTimeout())
337                         .payload(payload)
338                         .startCallback(this::onStart)
339                         .completeCallback(this::onComplete)
340                         .build();
341         // @formatter:on
342
343         // load the policy's operation
344         steps.add(new Step2(this, params, event));
345     }
346
347     /**
348      * Loads the preprocessor steps needed by the step that's at the front of the queue.
349      */
350     public void loadPreprocessorSteps() {
351         if (steps.size() >= MAX_STEPS) {
352             throw new IllegalStateException("too many steps");
353         }
354
355         final Step2 step = steps.peek();
356
357         // initialize the step so we can query its properties
358         step.init();
359
360         // determine if any A&AI queries are needed
361         boolean needCq = false;
362         boolean needPnf = false;
363         boolean needTenant = false;
364         boolean needTargetEntity = false;
365
366         for (String propName : step.getPropertyNames()) {
367             needCq = needCq || CQ_PROPERTIES.contains(propName);
368             needPnf = needPnf || PNF_PROPERTIES.contains(propName);
369             needTenant = needTenant || TENANT_PROPERTIES.contains(propName);
370             needTargetEntity = needTargetEntity || OperationProperties.AAI_TARGET_ENTITY.equals(propName);
371         }
372
373         /*
374          * The Policy's actual operation requires additional, implicit steps, such as
375          * locking and guards.
376          */
377         final boolean needPolicySteps = step.isPolicyStep();
378
379
380         /*
381          * NOTE: need to push steps onto the queue in the OPPOSITE order in which they
382          * must be performed.
383          */
384
385
386         // GUARD must be pushed first
387         if (needPolicySteps) {
388             steps.push(new GuardStep2(step, getClosedLoopControlName()));
389         }
390
391         // A&AI queries
392         if (needCq) {
393             steps.push(new AaiCqStep2(step));
394         }
395
396         if (needPnf) {
397             steps.push(new AaiGetPnfStep2(step));
398         }
399
400         if (needTenant) {
401             steps.push(new AaiGetTenantStep2(step));
402         }
403
404         // LOCK must be pushed after the queries
405         if (needPolicySteps) {
406             steps.push(new LockStep2(step));
407         }
408
409         // GET-TARGET-ENTITY should be pushed last
410         if (needTargetEntity) {
411             steps.push(new GetTargetEntityStep2(step));
412         }
413     }
414
415     /**
416      * Executes the first step in the queue.
417      *
418      * @return {@code true} if the step was started, {@code false} if it is no longer
419      *         needed (or if the queue is empty)
420      */
421     public boolean executeStep() {
422         attempts = 0;
423
424         Step2 step = steps.peek();
425         if (step == null) {
426             return false;
427         }
428
429         return step.start(getEndTimeMs() - System.currentTimeMillis());
430     }
431
432     /**
433      * Discards the current step, if any.
434      */
435     public void nextStep() {
436         steps.poll();
437     }
438
439     /**
440      * Increments the number of attempts.
441      */
442     public void bumpAttempts() {
443         ++attempts;
444     }
445
446     /**
447      * Determines if the TOSCA should be aborted due to the given outcome.
448      *
449      * @param outcome outcome to examine
450      * @return {@code true} if the TOSCA should be aborted, {@code false} otherwise
451      */
452     public boolean isAbort(OperationOutcome outcome) {
453         return (outcome.getResult() != PolicyResult.SUCCESS && ABORT_ACTORS.contains(outcome.getActor()));
454     }
455
456     /**
457      * Adds the outcome to the history.
458      *
459      * @param outcome outcome to add
460      */
461     public void addToHistory(OperationOutcome outcome) {
462         OperationOutcome2 last = partialHistory.peekLast();
463
464         if (last != null && last.getOutcome().getEnd() == null
465                         && last.getOutcome().isFor(outcome.getActor(), outcome.getOperation())) {
466             // last item was a "start" - remove it
467             partialHistory.removeLast();
468
469             if (fullHistory.peekLast() == last) {
470                 fullHistory.removeLast();
471             }
472         }
473
474         OperationOutcome2 outcome2 = new OperationOutcome2(outcome);
475         partialHistory.add(outcome2);
476         fullHistory.add(outcome2);
477     }
478
479     /**
480      * Makes a notification message for the current operation.
481      *
482      * @return a new notification
483      */
484     public VirtualControlLoopNotification makeNotification() {
485         VirtualControlLoopNotification notif = new VirtualControlLoopNotification(event);
486         notif.setNotification(ControlLoopNotificationType.OPERATION);
487         notif.setFrom("policy");
488         notif.setPolicyVersion(getPolicyVersion());
489
490         if (finalResult != null) {
491             return notif;
492         }
493
494         OperationOutcome2 last = partialHistory.peekLast();
495         if (last == null) {
496             return notif;
497         }
498
499         notif.setMessage(last.getClOperation().toHistory());
500         notif.setHistory(partialHistory.stream().map(OperationOutcome2::getClOperation).collect(Collectors.toList()));
501
502         return notif;
503     }
504
505     /**
506      * Delivers a notification to a topic.
507      *
508      * @param sinkName name of the topic sink
509      * @param notification notification to be published, or {@code null} if nothing is to
510      *        be published
511      * @param notificationType type of notification, used when logging error messages
512      * @param ruleName name of the rule doing the publishing
513      */
514     public <T> void deliver(String sinkName, T notification, String notificationType, String ruleName) {
515         try {
516             if (notification != null) {
517                 getPolicyEngineManager().deliver(sinkName, notification);
518             }
519
520         } catch (RuntimeException e) {
521             logger.warn("{}: {}.{}: manager={} exception publishing {}", getClosedLoopControlName(), getPolicyName(),
522                             ruleName, this, notificationType, e);
523         }
524     }
525
526     /**
527      * Get the last operation, as a message.
528      *
529      * @return the last operation, as a message
530      */
531     public String getOperationMessage() {
532         OperationOutcome2 last = fullHistory.peekLast();
533         return (last == null ? null : last.getClOperation().toMessage());
534     }
535
536     /**
537      * Stores an operation outcome in the DB.
538      *
539      * @param outcome operation outcome to store
540      */
541     public void storeInDataBase(OperationOutcome2 outcome) {
542         String targetEntity = getProperty(OperationProperties.AAI_TARGET_ENTITY);
543
544         getDataManager().store(requestIdStr, event, targetEntity, outcome.getClOperation());
545     }
546
547     /**
548      * Makes a control loop response.
549      *
550      * @param outcome operation outcome
551      * @return a new control loop response, or {@code null} if none is required
552      */
553     public ControlLoopResponse makeControlLoopResponse(OperationOutcome outcome) {
554         ControlLoopResponse clRsp = new ControlLoopResponse();
555         clRsp.setFrom(outcome.getActor());
556         clRsp.setTarget("DCAE");
557         clRsp.setClosedLoopControlName(event.getClosedLoopControlName());
558         clRsp.setPolicyName(event.getPolicyName());
559         clRsp.setPolicyVersion(event.getPolicyVersion());
560         clRsp.setRequestId(event.getRequestId());
561         clRsp.setVersion(event.getVersion());
562
563         Object obj = outcome.getResponse();
564         if (!(obj instanceof PciMessage)) {
565             return clRsp;
566         }
567
568         PciMessage msg = (PciMessage) obj;
569         if (msg.getBody() != null && msg.getBody().getOutput() != null) {
570             clRsp.setPayload(msg.getBody().getOutput().getPayload());
571         }
572
573         return clRsp;
574     }
575
576     /**
577      * An event onset/abatement.
578      *
579      * @param newEvent the event
580      * @return the status
581      */
582     public NewEventStatus onNewEvent(VirtualControlLoopEvent newEvent) {
583         try {
584             checkEventSyntax(newEvent);
585
586             if (newEvent.getClosedLoopEventStatus() == ControlLoopEventStatus.ONSET) {
587                 if (newEvent.equals(event)) {
588                     return NewEventStatus.FIRST_ONSET;
589                 }
590
591                 numOnsets++;
592                 return NewEventStatus.SUBSEQUENT_ONSET;
593
594             } else {
595                 if (abatement == null) {
596                     abatement = newEvent;
597                     numAbatements++;
598                     return NewEventStatus.FIRST_ABATEMENT;
599                 } else {
600                     numAbatements++;
601                     return NewEventStatus.SUBSEQUENT_ABATEMENT;
602                 }
603             }
604         } catch (ControlLoopException e) {
605             logger.error("{}: onNewEvent threw an exception", this, e);
606             return NewEventStatus.SYNTAX_ERROR;
607         }
608     }
609
610     /**
611      * Check an event syntax.
612      *
613      * @param event the event syntax
614      * @throws ControlLoopException if an error occurs
615      */
616     protected void checkEventSyntax(VirtualControlLoopEvent event) throws ControlLoopException {
617         validateStatus(event);
618         if (StringUtils.isBlank(event.getClosedLoopControlName())) {
619             throw new ControlLoopException("No control loop name");
620         }
621         if (event.getRequestId() == null) {
622             throw new ControlLoopException("No request ID");
623         }
624         if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ABATED) {
625             return;
626         }
627         if (StringUtils.isBlank(event.getTarget())) {
628             throw new ControlLoopException("No target field");
629         } else if (!VALID_TARGETS.contains(event.getTarget().toLowerCase())) {
630             throw new ControlLoopException("target field invalid");
631         }
632         validateAaiData(event);
633     }
634
635     private void validateStatus(VirtualControlLoopEvent event) throws ControlLoopException {
636         if (event.getClosedLoopEventStatus() != ControlLoopEventStatus.ONSET
637                         && event.getClosedLoopEventStatus() != ControlLoopEventStatus.ABATED) {
638             throw new ControlLoopException("Invalid value in closedLoopEventStatus");
639         }
640     }
641
642     private void validateAaiData(VirtualControlLoopEvent event) throws ControlLoopException {
643         Map<String, String> eventAai = event.getAai();
644         if (eventAai == null) {
645             throw new ControlLoopException("AAI is null");
646         }
647         if (event.getTargetType() == null) {
648             throw new ControlLoopException("The Target type is null");
649         }
650         switch (event.getTargetType()) {
651             case VM:
652             case VNF:
653                 validateAaiVmVnfData(eventAai);
654                 return;
655             case PNF:
656                 validateAaiPnfData(eventAai);
657                 return;
658             default:
659                 throw new ControlLoopException("The target type is not supported");
660         }
661     }
662
663     private void validateAaiVmVnfData(Map<String, String> eventAai) throws ControlLoopException {
664         if (eventAai.get(GENERIC_VNF_VNF_ID) == null && eventAai.get(VSERVER_VSERVER_NAME) == null
665                         && eventAai.get(GENERIC_VNF_VNF_NAME) == null) {
666             throw new ControlLoopException(
667                             "generic-vnf.vnf-id or generic-vnf.vnf-name or vserver.vserver-name information missing");
668         }
669     }
670
671     private void validateAaiPnfData(Map<String, String> eventAai) throws ControlLoopException {
672         if (eventAai.get(PNF_NAME) == null) {
673             throw new ControlLoopException("AAI PNF object key pnf-name is missing");
674         }
675     }
676
677     /**
678      * Is closed loop disabled for an event.
679      *
680      * @param event the event
681      * @return <code>true</code> if the control loop is disabled, <code>false</code>
682      *         otherwise
683      */
684     private static boolean isClosedLoopDisabled(VirtualControlLoopEvent event) {
685         Map<String, String> aai = event.getAai();
686         return (isAaiTrue(aai.get(VSERVER_IS_CLOSED_LOOP_DISABLED))
687                         || isAaiTrue(aai.get(GENERIC_VNF_IS_CLOSED_LOOP_DISABLED))
688                         || isAaiTrue(aai.get(PNF_IS_IN_MAINT)));
689     }
690
691     /**
692      * Does provisioning status, for an event, have a value other than ACTIVE.
693      *
694      * @param event the event
695      * @return {@code true} if the provisioning status is neither ACTIVE nor {@code null},
696      *         {@code false} otherwise
697      */
698     private static boolean isProvStatusInactive(VirtualControlLoopEvent event) {
699         Map<String, String> aai = event.getAai();
700         return !(PROV_STATUS_ACTIVE.equals(aai.getOrDefault(VSERVER_PROV_STATUS, PROV_STATUS_ACTIVE))
701                         && PROV_STATUS_ACTIVE.equals(aai.getOrDefault(GENERIC_VNF_PROV_STATUS, PROV_STATUS_ACTIVE)));
702     }
703
704     /**
705      * Determines the boolean value represented by the given AAI field value.
706      *
707      * @param aaiValue value to be examined
708      * @return the boolean value represented by the field value, or {@code false} if the
709      *         value is {@code null}
710      */
711     private static boolean isAaiTrue(String aaiValue) {
712         return (aaiValue != null && TRUE_VALUES.contains(aaiValue.toLowerCase()));
713     }
714
715     @Override
716     public void onStart(OperationOutcome outcome) {
717         super.onStart(outcome);
718         workMem.update(factHandle, this);
719     }
720
721     @Override
722     public void onComplete(OperationOutcome outcome) {
723         super.onComplete(outcome);
724         workMem.update(factHandle, this);
725     }
726
727     @Getter
728     @ToString
729     public class OperationOutcome2 {
730         private final int attempt;
731         private final OperationOutcome outcome;
732         private final ControlLoopOperation clOperation;
733
734         /**
735          * Constructs the object.
736          *
737          * @param outcome outcome of the operation
738          */
739         public OperationOutcome2(OperationOutcome outcome) {
740             this.outcome = outcome;
741             this.attempt = attempts;
742
743             clOperation = outcome.toControlLoopOperation();
744             clOperation.setTarget(policy.getTarget().toString());
745
746             if (outcome.getEnd() == null) {
747                 clOperation.setOutcome("Started");
748             } else if (clOperation.getOutcome() == null) {
749                 clOperation.setOutcome("");
750             }
751         }
752     }
753
754     // these following methods may be overridden by junit tests
755
756     protected PolicyEngine getPolicyEngineManager() {
757         return PolicyEngineConstants.getManager();
758     }
759 }