Allow mixed case in check of prov-status
[policy/drools-applications.git] / controlloop / common / eventmanager / src / main / java / org / onap / policy / controlloop / eventmanager / ControlLoopOperationManager2.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2017-2020 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2019 Huawei Technologies Co., Ltd. All rights reserved.
7  * Modifications Copyright (C) 2019 Tech Mahindra
8  * Modifications Copyright (C) 2019 Bell Canada.
9  * ================================================================================
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.policy.controlloop.eventmanager;
25
26 import java.io.Serializable;
27 import java.time.Instant;
28 import java.util.Deque;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.concurrent.CancellationException;
33 import java.util.concurrent.CompletableFuture;
34 import java.util.concurrent.ConcurrentLinkedDeque;
35 import java.util.concurrent.Executor;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.atomic.AtomicReference;
38 import java.util.stream.Collectors;
39 import lombok.AccessLevel;
40 import lombok.Getter;
41 import lombok.ToString;
42 import org.onap.policy.aai.AaiConstants;
43 import org.onap.policy.aai.AaiCqResponse;
44 import org.onap.policy.controlloop.ControlLoopOperation;
45 import org.onap.policy.controlloop.ControlLoopResponse;
46 import org.onap.policy.controlloop.VirtualControlLoopEvent;
47 import org.onap.policy.controlloop.actor.guard.GuardActor;
48 import org.onap.policy.controlloop.actor.sdnr.SdnrActor;
49 import org.onap.policy.controlloop.actorserviceprovider.OperationOutcome;
50 import org.onap.policy.controlloop.actorserviceprovider.OperationResult;
51 import org.onap.policy.controlloop.actorserviceprovider.TargetType;
52 import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
53 import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
54 import org.onap.policy.controlloop.actorserviceprovider.pipeline.PipelineUtil;
55 import org.onap.policy.drools.domain.models.operational.OperationalTarget;
56 import org.onap.policy.sdnr.PciMessage;
57 import org.slf4j.Logger;
58 import org.slf4j.LoggerFactory;
59
60 /**
61  * Manages a single Operation for a single event. Once this has been created,
62  * {@link #start()} should be invoked, and then {@link #nextStep()} should be invoked
63  * continually until it returns {@code false}, indicating that all steps have completed.
64  */
65 @ToString(onlyExplicitlyIncluded = true)
66 public class ControlLoopOperationManager2 implements Serializable {
67     private static final long serialVersionUID = -3773199283624595410L;
68     private static final Logger logger = LoggerFactory.getLogger(ControlLoopOperationManager2.class);
69     private static final String CL_TIMEOUT_ACTOR = "-CL-TIMEOUT-";
70     public static final String LOCK_ACTOR = "LOCK";
71     public static final String LOCK_OPERATION = "Lock";
72     private static final String GUARD_ACTOR = GuardActor.NAME;
73     public static final String VSERVER_VSERVER_NAME = "vserver.vserver-name";
74     public static final String GENERIC_VNF_VNF_NAME = "generic-vnf.vnf-name";
75     public static final String GENERIC_VNF_VNF_ID = "generic-vnf.vnf-id";
76     public static final String PNF_NAME = "pnf.pnf-name";
77
78     // @formatter:off
79     public enum State {
80         ACTIVE,
81         LOCK_DENIED,
82         LOCK_LOST,
83         GUARD_STARTED,
84         GUARD_PERMITTED,
85         GUARD_DENIED,
86         OPERATION_STARTED,
87         OPERATION_SUCCESS,
88         OPERATION_FAILURE,
89         CONTROL_LOOP_TIMEOUT
90     }
91     // @formatter:on
92
93     private final transient ManagerContext operContext;
94     private final transient ControlLoopEventContext eventContext;
95     private final org.onap.policy.drools.domain.models.operational.Operation policy;
96
97     @Getter
98     @ToString.Include
99     private State state = State.ACTIVE;
100
101     @ToString.Include
102     private final String requestId;
103
104     @ToString.Include
105     private final String policyId;
106
107     /**
108      * Bumped each time the "complete" callback is invoked by the Actor, provided it's for
109      * this operation.
110      */
111     @ToString.Include
112     private int attempts = 0;
113
114     private final Deque<Operation> operationHistory = new ConcurrentLinkedDeque<>();
115
116     /**
117      * Set to {@code true} to prevent the last item in {@link #operationHistory} from
118      * being included in the outcome of {@link #getHistory()}. Used when the operation
119      * aborts prematurely due to lock-denied, guard-denied, etc.
120      */
121     private boolean holdLast = false;
122
123     /**
124      * Queue of outcomes yet to be processed. Outcomes are added to this each time the
125      * "start" or "complete" callback is invoked.
126      */
127     @Getter(AccessLevel.PROTECTED)
128     private final transient Deque<OperationOutcome> outcomes = new ConcurrentLinkedDeque<>();
129
130     /**
131      * Used to cancel the running operation.
132      */
133     @Getter(AccessLevel.PROTECTED)
134     private transient CompletableFuture<OperationOutcome> future = null;
135
136     /**
137      * Target entity. Determined after the lock is granted, though it may require the
138      * custom query to be performed first.
139      */
140     @Getter
141     private String targetEntity;
142
143     @Getter(AccessLevel.PROTECTED)
144     private final transient ControlLoopOperationParams params;
145     private final transient PipelineUtil taskUtil;
146
147     @Getter
148     private ControlLoopResponse controlLoopResponse;
149
150     /**
151      * Time when the lock was first requested.
152      */
153     private transient AtomicReference<Instant> lockStart = new AtomicReference<>();
154
155     // values extracted from the policy
156     @Getter
157     private final String actor;
158     @Getter
159     private final String operation;
160
161     private final String targetStr;
162     private final OperationalTarget target;
163
164
165     /**
166      * Construct an instance.
167      *
168      * @param operContext this operation's context
169      * @param context event context
170      * @param operation2 operation's policy
171      * @param executor executor for the Operation
172      */
173     public ControlLoopOperationManager2(ManagerContext operContext, ControlLoopEventContext context,
174                     org.onap.policy.drools.domain.models.operational.Operation operation2, Executor executor) {
175
176         this.operContext = operContext;
177         this.eventContext = context;
178         this.policy = operation2;
179         this.requestId = context.getEvent().getRequestId().toString();
180         this.policyId = "" + operation2.getId();
181         this.actor = operation2.getActorOperation().getActor();
182         this.operation = operation2.getActorOperation().getOperation();
183         this.target = operation2.getActorOperation().getTarget();
184
185         String targetType = (target != null ? target.getTargetType() : null);
186         Map<String, String> entityIds = (target != null ? target.getEntityIds() : null);
187
188         // TODO encode()?
189         this.targetStr = (target != null ? target.toString() : null);
190
191         // @formatter:off
192         params = ControlLoopOperationParams.builder()
193                         .actorService(operContext.getActorService())
194                         .actor(actor)
195                         .operation(operation)
196                         .context(context)
197                         .executor(executor)
198                         .targetType(TargetType.toTargetType(targetType))
199                         .targetEntityIds(entityIds)
200                         .startCallback(this::onStart)
201                         .completeCallback(this::onComplete)
202                         .build();
203         // @formatter:on
204
205         taskUtil = new PipelineUtil(params);
206     }
207
208     //
209     // Internal class used for tracking
210     //
211     @Getter
212     @ToString
213     private class Operation implements Serializable {
214         private static final long serialVersionUID = 1L;
215
216         private int attempt;
217         private OperationResult policyResult;
218         private ControlLoopOperation clOperation;
219         private ControlLoopResponse clResponse;
220
221         /**
222          * Constructs the object.
223          *
224          * @param outcome outcome of the operation
225          */
226         public Operation(OperationOutcome outcome) {
227             attempt = ControlLoopOperationManager2.this.attempts;
228             policyResult = outcome.getResult();
229             clOperation = outcome.toControlLoopOperation();
230             clOperation.setTarget(targetStr);
231             clResponse = makeControlLoopResponse(outcome);
232
233             if (outcome.getEnd() == null) {
234                 clOperation.setOutcome("Started");
235             } else if (clOperation.getOutcome() == null) {
236                 clOperation.setOutcome("");
237             }
238         }
239     }
240
241     /**
242      * Start the operation, first acquiring any locks that are needed. This should not
243      * throw any exceptions, but will, instead, invoke the callbacks with exceptions.
244      *
245      * @param remainingMs time remaining, in milliseconds, for the control loop
246      */
247     @SuppressWarnings("unchecked")
248     public synchronized void start(long remainingMs) {
249         // this is synchronized while we update "future"
250
251         try {
252             // provide a default, in case something fails before requestLock() is called
253             lockStart.set(Instant.now());
254
255             // @formatter:off
256             future = taskUtil.sequence(
257                 this::detmTarget,
258                 this::requestLock,
259                 this::startOperation);
260             // @formatter:on
261
262             // handle any exceptions that may be thrown, set timeout, and handle timeout
263
264             // @formatter:off
265             future.exceptionally(this::handleException)
266                     .orTimeout(remainingMs, TimeUnit.MILLISECONDS)
267                     .exceptionally(this::handleTimeout);
268             // @formatter:on
269
270         } catch (RuntimeException e) {
271             handleException(e);
272         }
273     }
274
275     /**
276      * Start the operation, after the lock has been acquired.
277      *
278      * @return CompletableFuture for the operation being started
279      */
280     private CompletableFuture<OperationOutcome> startOperation() {
281         // @formatter:off
282         ControlLoopOperationParams params2 = params.toBuilder()
283                     .payload(new LinkedHashMap<>())
284                     .retry(policy.getRetries())
285                     .timeoutSec(policy.getTimeout())
286                     .targetEntity(targetEntity)
287                     .build();
288         // @formatter:on
289
290         if (policy.getActorOperation().getPayload() != null) {
291             params2.getPayload().putAll(policy.getActorOperation().getPayload());
292         }
293
294         return params2.start();
295     }
296
297     /**
298      * Handles exceptions that may be generated.
299      *
300      * @param thrown exception that was generated
301      * @return {@code null}
302      */
303     private OperationOutcome handleException(Throwable thrown) { // NOSONAR
304         /*
305          * disabling sonar about returning the same value because we prefer the code to be
306          * structured this way
307          */
308
309         if (thrown instanceof CancellationException || thrown.getCause() instanceof CancellationException) {
310             return null;
311         }
312
313         logger.warn("{}.{}: exception starting operation for {}", actor, operation, requestId, thrown);
314         OperationOutcome outcome = taskUtil.setOutcome(params.makeOutcome(), thrown);
315         outcome.setStart(lockStart.get());
316         outcome.setEnd(Instant.now());
317         outcome.setFinalOutcome(true);
318         onComplete(outcome);
319
320         // this outcome is not used so just return "null"
321         return null;
322     }
323
324     /**
325      * Handles control loop timeout exception.
326      *
327      * @param thrown exception that was generated
328      * @return {@code null}
329      */
330     private OperationOutcome handleTimeout(Throwable thrown) {
331         logger.warn("{}.{}: control loop timeout for {}", actor, operation, requestId, thrown);
332
333         OperationOutcome outcome = taskUtil.setOutcome(params.makeOutcome(), thrown);
334         outcome.setActor(CL_TIMEOUT_ACTOR);
335         outcome.setOperation(null);
336         outcome.setStart(lockStart.get());
337         outcome.setEnd(Instant.now());
338         outcome.setFinalOutcome(true);
339         onComplete(outcome);
340
341         // cancel the operation, if it's still running
342         future.cancel(false);
343
344         // this outcome is not used so just return "null"
345         return null;
346     }
347
348     /**
349      * Cancels the operation.
350      */
351     public void cancel() {
352         synchronized (this) {
353             if (future == null) {
354                 return;
355             }
356         }
357
358         future.cancel(false);
359     }
360
361     /**
362      * Requests a lock on the {@link #targetEntity}.
363      *
364      * @return a future to await the lock
365      */
366     private CompletableFuture<OperationOutcome> requestLock() {
367         /*
368          * Failures are handled via the callback, and successes are discarded by
369          * sequence(), without passing them to onComplete().
370          *
371          * Return a COPY of the future so that if we try to cancel it, we'll only cancel
372          * the copy, not the original. This is done by tacking thenApply() onto the end.
373          */
374         lockStart.set(Instant.now());
375         return operContext.requestLock(targetEntity, this::lockUnavailable).thenApply(outcome -> outcome);
376     }
377
378     /**
379      * Indicates that the lock on the target entity is unavailable.
380      *
381      * @param outcome lock outcome
382      */
383     private void lockUnavailable(OperationOutcome outcome) {
384
385         // Note: NEVER invoke onStart() for locks; only invoke onComplete()
386         onComplete(outcome);
387
388         /*
389          * Now that we've added the lock outcome to the queue, ensure the future is
390          * canceled, which may, itself, generate an operation outcome.
391          */
392         cancel();
393     }
394
395     /**
396      * Handles responses provided via the "start" callback. Note: this is never be invoked
397      * for locks; only {@link #onComplete(OperationOutcome)} is invoked for locks.
398      *
399      * @param outcome outcome provided to the callback
400      */
401     private void onStart(OperationOutcome outcome) {
402         if (outcome.isFor(actor, operation) || GUARD_ACTOR.equals(outcome.getActor())) {
403             addOutcome(outcome);
404         }
405     }
406
407     /**
408      * Handles responses provided via the "complete" callback. Note: this is never invoked
409      * for "successful" locks.
410      *
411      * @param outcome outcome provided to the callback
412      */
413     private void onComplete(OperationOutcome outcome) {
414
415         switch (outcome.getActor()) {
416             case LOCK_ACTOR:
417             case GUARD_ACTOR:
418             case CL_TIMEOUT_ACTOR:
419                 addOutcome(outcome);
420                 break;
421
422             default:
423                 if (outcome.isFor(actor, operation)) {
424                     addOutcome(outcome);
425                 }
426                 break;
427         }
428     }
429
430     /**
431      * Adds an outcome to {@link #outcomes}.
432      *
433      * @param outcome outcome to be added
434      */
435     private synchronized void addOutcome(OperationOutcome outcome) {
436         /*
437          * This is synchronized to prevent nextStep() from invoking processOutcome() at
438          * the same time.
439          */
440
441         logger.debug("added outcome={} for {}", outcome, requestId);
442         outcomes.add(outcome);
443
444         if (outcomes.peekFirst() == outcomes.peekLast()) {
445             // this is the first outcome in the queue - process it
446             processOutcome();
447         }
448     }
449
450     /**
451      * Looks for the next step in the queue.
452      *
453      * @return {@code true} if more responses are expected, {@code false} otherwise
454      */
455     public synchronized boolean nextStep() {
456         switch (state) {
457             case LOCK_DENIED:
458             case LOCK_LOST:
459             case GUARD_DENIED:
460             case CONTROL_LOOP_TIMEOUT:
461                 holdLast = false;
462                 return false;
463             default:
464                 break;
465         }
466
467         OperationOutcome outcome = outcomes.peek();
468         if (outcome == null) {
469             // empty queue
470             return true;
471         }
472
473         if (outcome.isFinalOutcome() && outcome.isFor(actor, operation)) {
474             controlLoopResponse = null;
475             return false;
476         }
477
478         // first item has been processed, remove it
479         outcomes.remove();
480         if (!outcomes.isEmpty()) {
481             // have a new "first" item - process it
482             processOutcome();
483         }
484
485         return true;
486     }
487
488     /**
489      * Processes the first item in {@link #outcomes}. Sets the state, increments
490      * {@link #attempts}, if appropriate, and stores the operation history in the DB.
491      */
492     private synchronized void processOutcome() {
493         OperationOutcome outcome = outcomes.peek();
494         logger.debug("process outcome={} for {}", outcome, requestId);
495
496         controlLoopResponse = null;
497
498         switch (outcome.getActor()) {
499
500             case CL_TIMEOUT_ACTOR:
501                 state = State.CONTROL_LOOP_TIMEOUT;
502                 processAbort(outcome, OperationResult.FAILURE, "Control loop timed out");
503                 break;
504
505             case LOCK_ACTOR:
506                 // lock is no longer available
507                 if (state == State.ACTIVE) {
508                     state = State.LOCK_DENIED;
509                     storeFailureInDataBase(outcome, OperationResult.FAILURE_GUARD, "Operation denied by Lock");
510                 } else {
511                     state = State.LOCK_LOST;
512                     processAbort(outcome, OperationResult.FAILURE, "Operation aborted by Lock");
513                 }
514                 break;
515
516             case GUARD_ACTOR:
517                 if (outcome.getEnd() == null) {
518                     state = State.GUARD_STARTED;
519                 } else if (outcome.getResult() == OperationResult.SUCCESS) {
520                     state = State.GUARD_PERMITTED;
521                 } else {
522                     state = State.GUARD_DENIED;
523                     storeFailureInDataBase(outcome, OperationResult.FAILURE_GUARD, "Operation denied by Guard");
524                 }
525                 break;
526
527             default:
528                 if (outcome.getEnd() == null) {
529                     // operation started
530                     ++attempts;
531                     state = State.OPERATION_STARTED;
532
533                 } else {
534                     /*
535                      * Operation completed. If the last entry was a "start" (i.e., "end" field
536                      * is null), then replace it. Otherwise, just add the completion.
537                      */
538                     state = (outcome.getResult() == OperationResult.SUCCESS ? State.OPERATION_SUCCESS
539                                     : State.OPERATION_FAILURE);
540                     controlLoopResponse = makeControlLoopResponse(outcome);
541                     if (!operationHistory.isEmpty() && operationHistory.peekLast().getClOperation().getEnd() == null) {
542                         operationHistory.removeLast();
543                     }
544                 }
545
546                 operationHistory.add(new Operation(outcome));
547                 storeOperationInDataBase();
548                 break;
549         }
550
551         // indicate that this has changed
552         operContext.updated(this);
553     }
554
555     /**
556      * Processes an operation abort, updating the DB record, if an operation has been
557      * started.
558      *
559      * @param outcome operation outcome
560      * @param result result to put into the DB
561      * @param message message to put into the DB
562      */
563     private void processAbort(OperationOutcome outcome, OperationResult result, String message) {
564         if (operationHistory.isEmpty() || operationHistory.peekLast().getClOperation().getEnd() != null) {
565             // last item was not a "start" operation
566
567             // NOTE: do NOT generate control loop response since operation was not started
568
569             storeFailureInDataBase(outcome, result, message);
570             return;
571         }
572
573         // last item was a "start" operation - replace it with a failure
574         final Operation operOrig = operationHistory.removeLast();
575
576         // use start time from the operation, itself
577         if (operOrig != null && operOrig.getClOperation() != null) {
578             outcome.setStart(operOrig.getClOperation().getStart());
579         }
580
581         controlLoopResponse = makeControlLoopResponse(outcome);
582
583         storeFailureInDataBase(outcome, result, message);
584     }
585
586     /**
587      * Makes a control loop response.
588      *
589      * @param outcome operation outcome
590      * @return a new control loop response, or {@code null} if none is required
591      */
592     protected ControlLoopResponse makeControlLoopResponse(OperationOutcome outcome) {
593
594         // only generate response for certain actors.
595         if (outcome == null || !actor.equals(SdnrActor.NAME)) {
596             return null;
597         }
598
599         VirtualControlLoopEvent event = eventContext.getEvent();
600
601         ControlLoopResponse clRsp = new ControlLoopResponse();
602         clRsp.setFrom(actor);
603         clRsp.setTarget("DCAE");
604         clRsp.setClosedLoopControlName(event.getClosedLoopControlName());
605         clRsp.setPolicyName(event.getPolicyName());
606         clRsp.setPolicyVersion(event.getPolicyVersion());
607         clRsp.setRequestId(event.getRequestId());
608         clRsp.setVersion(event.getVersion());
609
610         PciMessage msg = outcome.getResponse();
611         if (msg != null && msg.getBody() != null && msg.getBody().getOutput() != null) {
612             clRsp.setPayload(msg.getBody().getOutput().getPayload());
613         }
614
615         return clRsp;
616     }
617
618     /**
619      * Get the operation, as a message.
620      *
621      * @return the operation, as a message
622      */
623     public String getOperationMessage() {
624         Operation last = operationHistory.peekLast();
625         return (last == null ? null : last.getClOperation().toMessage());
626     }
627
628     /**
629      * Gets the operation result.
630      *
631      * @return the operation result
632      */
633     public OperationResult getOperationResult() {
634         Operation last = operationHistory.peekLast();
635         return (last == null ? OperationResult.FAILURE_EXCEPTION : last.getPolicyResult());
636     }
637
638     /**
639      * Get the latest operation history.
640      *
641      * @return the latest operation history
642      */
643     public String getOperationHistory() {
644         Operation last = operationHistory.peekLast();
645         return (last == null ? null : last.clOperation.toHistory());
646     }
647
648     /**
649      * Get the history.
650      *
651      * @return the list of control loop operations
652      */
653     public List<ControlLoopOperation> getHistory() {
654         Operation last = (holdLast ? operationHistory.removeLast() : null);
655
656         List<ControlLoopOperation> result = operationHistory.stream().map(Operation::getClOperation)
657                         .map(ControlLoopOperation::new).collect(Collectors.toList());
658
659         if (last != null) {
660             operationHistory.add(last);
661         }
662
663         return result;
664     }
665
666     /**
667      * Stores a failure in the DB.
668      *
669      * @param outcome operation outcome
670      * @param result result to put into the DB
671      * @param message message to put into the DB
672      */
673     private void storeFailureInDataBase(OperationOutcome outcome, OperationResult result, String message) {
674         // don't include this in history yet
675         holdLast = true;
676
677         outcome.setActor(actor);
678         outcome.setOperation(operation);
679         outcome.setMessage(message);
680         outcome.setResult(result);
681
682         operationHistory.add(new Operation(outcome));
683         storeOperationInDataBase();
684     }
685
686     /**
687      * Stores the latest operation in the DB.
688      */
689     private void storeOperationInDataBase() {
690         operContext.getDataManager().store(requestId, eventContext.getEvent(), targetEntity,
691                         operationHistory.peekLast().getClOperation());
692     }
693
694     /**
695      * Determines the target entity.
696      *
697      * @return a future to determine the target entity, or {@code null} if the entity has
698      *         already been determined
699      */
700     protected CompletableFuture<OperationOutcome> detmTarget() {
701         if (target == null) {
702             throw new IllegalArgumentException("The target is null");
703         }
704
705         if (target.getTargetType() == null) {
706             throw new IllegalArgumentException("The target type is null");
707         }
708
709         switch (TargetType.toTargetType(target.getTargetType())) {
710             case PNF:
711                 return detmPnfTarget();
712             case VM:
713             case VNF:
714             case VFMODULE:
715                 return detmVfModuleTarget();
716             default:
717                 throw new IllegalArgumentException("The target type is not supported");
718         }
719     }
720
721     /**
722      * Determines the PNF target entity.
723      *
724      * @return a future to determine the target entity, or {@code null} if the entity has
725      *         already been determined
726      */
727     private CompletableFuture<OperationOutcome> detmPnfTarget() {
728         if (!PNF_NAME.equalsIgnoreCase(eventContext.getEvent().getTarget())) {
729             throw new IllegalArgumentException("Target does not match target type");
730         }
731
732         targetEntity = eventContext.getEnrichment().get(PNF_NAME);
733         if (targetEntity == null) {
734             throw new IllegalArgumentException("AAI section is missing " + PNF_NAME);
735         }
736
737         return null;
738     }
739
740     /**
741      * Determines the VF Module target entity.
742      *
743      * @return a future to determine the target entity, or {@code null} if the entity has
744      *         already been determined
745      */
746     private CompletableFuture<OperationOutcome> detmVfModuleTarget() {
747         String targetFieldName = eventContext.getEvent().getTarget();
748         if (targetFieldName == null) {
749             throw new IllegalArgumentException("Target is null");
750         }
751
752         switch (targetFieldName.toLowerCase()) {
753             case VSERVER_VSERVER_NAME:
754                 targetEntity = eventContext.getEnrichment().get(VSERVER_VSERVER_NAME);
755                 break;
756             case GENERIC_VNF_VNF_ID:
757                 targetEntity = eventContext.getEnrichment().get(GENERIC_VNF_VNF_ID);
758                 break;
759             case GENERIC_VNF_VNF_NAME:
760                 return detmVnfName();
761             default:
762                 throw new IllegalArgumentException("Target does not match target type");
763         }
764
765         if (targetEntity == null) {
766             throw new IllegalArgumentException("Enrichment data is missing " + targetFieldName);
767         }
768
769         return null;
770     }
771
772     /**
773      * Determines the VNF Name target entity.
774      *
775      * @return a future to determine the target entity, or {@code null} if the entity has
776      *         already been determined
777      */
778     @SuppressWarnings("unchecked")
779     private CompletableFuture<OperationOutcome> detmVnfName() {
780         // if the onset is enriched with the vnf-id, we don't need an A&AI response
781         targetEntity = eventContext.getEnrichment().get(GENERIC_VNF_VNF_ID);
782         if (targetEntity != null) {
783             return null;
784         }
785
786         // vnf-id was not in the onset - obtain it via the custom query
787
788         // @formatter:off
789         ControlLoopOperationParams cqparams = params.toBuilder()
790                         .actor(AaiConstants.ACTOR_NAME)
791                         .operation(AaiCqResponse.OPERATION)
792                         .targetEntity("")
793                         .build();
794         // @formatter:on
795
796         // perform custom query and then extract the VNF ID from it
797         return taskUtil.sequence(() -> eventContext.obtain(AaiCqResponse.CONTEXT_KEY, cqparams),
798                         this::extractVnfFromCq);
799     }
800
801     /**
802      * Extracts the VNF Name target entity from the custom query data.
803      *
804      * @return {@code null}
805      */
806     private CompletableFuture<OperationOutcome> extractVnfFromCq() {
807         // already have the CQ data
808         AaiCqResponse cq = eventContext.getProperty(AaiCqResponse.CONTEXT_KEY);
809         if (cq.getDefaultGenericVnf() == null) {
810             throw new IllegalArgumentException("No vnf-id found");
811         }
812
813         targetEntity = cq.getDefaultGenericVnf().getVnfId();
814         if (targetEntity == null) {
815             throw new IllegalArgumentException("No vnf-id found");
816         }
817
818         return null;
819     }
820 }