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