8b06a968180c40332435f8b75d8d462110b98810
[policy/drools-applications.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * controlloop event manager
4  * ================================================================================
5  * Copyright (C) 2017-2019 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.controlloop.eventmanager;
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
27 import java.io.Serializable;
28 import java.io.UnsupportedEncodingException;
29 import java.net.URLDecoder;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.LinkedList;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.NoSuchElementException;
39 import java.util.Set;
40 import java.util.UUID;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43 import org.apache.commons.lang3.StringUtils;
44 import org.onap.policy.aai.AaiCqResponse;
45 import org.onap.policy.aai.AaiGetVnfResponse;
46 import org.onap.policy.aai.AaiGetVserverResponse;
47 import org.onap.policy.aai.AaiManager;
48 import org.onap.policy.aai.AaiNqInstanceFilters;
49 import org.onap.policy.aai.AaiNqNamedQuery;
50 import org.onap.policy.aai.AaiNqQueryParameters;
51 import org.onap.policy.aai.AaiNqRequest;
52 import org.onap.policy.aai.AaiNqResponse;
53 import org.onap.policy.aai.AaiNqResponseWrapper;
54 import org.onap.policy.aai.AaiNqVServer;
55 import org.onap.policy.aai.util.AaiException;
56 import org.onap.policy.controlloop.ControlLoopEventStatus;
57 import org.onap.policy.controlloop.ControlLoopException;
58 import org.onap.policy.controlloop.ControlLoopNotificationType;
59 import org.onap.policy.controlloop.ControlLoopOperation;
60 import org.onap.policy.controlloop.VirtualControlLoopEvent;
61 import org.onap.policy.controlloop.VirtualControlLoopNotification;
62 import org.onap.policy.controlloop.policy.FinalResult;
63 import org.onap.policy.controlloop.policy.Policy;
64 import org.onap.policy.controlloop.processor.ControlLoopProcessor;
65 import org.onap.policy.drools.core.lock.Lock;
66 import org.onap.policy.drools.core.lock.LockCallback;
67 import org.onap.policy.drools.core.lock.LockImpl;
68 import org.onap.policy.drools.core.lock.LockState;
69 import org.onap.policy.drools.system.PolicyEngineConstants;
70 import org.onap.policy.drools.utils.Pair;
71 import org.onap.policy.rest.RestManager;
72 import org.onap.policy.so.util.Serialization;
73 import org.slf4j.Logger;
74 import org.slf4j.LoggerFactory;
75
76 public class ControlLoopEventManager implements Serializable {
77     public static final String PROV_STATUS_ACTIVE = "ACTIVE";
78     private static final String VM_NAME = "VM_NAME";
79     private static final String VNF_NAME = "VNF_NAME";
80     public static final String GENERIC_VNF_VNF_ID = "generic-vnf.vnf-id";
81     public static final String GENERIC_VNF_VNF_NAME = "generic-vnf.vnf-name";
82     public static final String VSERVER_VSERVER_NAME = "vserver.vserver-name";
83     public static final String GENERIC_VNF_IS_CLOSED_LOOP_DISABLED = "generic-vnf.is-closed-loop-disabled";
84     public static final String VSERVER_IS_CLOSED_LOOP_DISABLED = "vserver.is-closed-loop-disabled";
85     private static final String PNF_IS_IN_MAINT = "pnf.in-maint";
86     public static final String GENERIC_VNF_PROV_STATUS = "generic-vnf.prov-status";
87     public static final String VSERVER_PROV_STATUS = "vserver.prov-status";
88     public static final String PNF_ID = "pnf.pnf-id";
89     public static final String PNF_NAME = "pnf.pnf-name";
90
91     public static final String AAI_URL = "aai.url";
92     public static final String AAI_USERNAME_PROPERTY = "aai.username";
93     public static final String AAI_PASS_PROPERTY = "aai.password";
94
95     private static final String QUERY_AAI_ERROR_MSG = "Exception from queryAai: ";
96
97     /**
98      * Additional time, in seconds, to add to a "lock" request. This ensures that the lock won't expire right before an
99      * operation completes.
100      */
101     private static final int ADDITIONAL_LOCK_SEC = 60;
102
103     private static final Logger logger = LoggerFactory.getLogger(ControlLoopEventManager.class);
104
105     private static final long serialVersionUID = -1216568161322872641L;
106
107     private static final Set<String> VALID_TARGETS;
108
109     static {
110         VALID_TARGETS = Collections.unmodifiableSet(new HashSet<>(
111                 Stream.of(VM_NAME, VNF_NAME, VSERVER_VSERVER_NAME, GENERIC_VNF_VNF_ID, GENERIC_VNF_VNF_NAME, PNF_NAME)
112                         .map(String::toLowerCase).collect(Collectors.toList())));
113     }
114
115     public final String closedLoopControlName;
116     private final UUID requestId;
117
118     private String controlLoopResult;
119     private ControlLoopProcessor processor = null;
120     private VirtualControlLoopEvent onset;
121     private Integer numOnsets = 0;
122     private Integer numAbatements = 0;
123     private VirtualControlLoopEvent abatement;
124     private FinalResult controlLoopTimedOut = null;
125
126     private boolean isActivated = false;
127     private LinkedList<ControlLoopOperation> controlLoopHistory = new LinkedList<>();
128     private ControlLoopOperationManager currentOperation = null;
129     private ControlLoopOperationManager lastOperationManager = null;
130     private transient Lock targetLock = null;
131     private AaiGetVnfResponse vnfResponse = null;
132     private AaiGetVserverResponse vserverResponse = null;
133     private boolean useTargetLock = true;
134
135     /**
136      * Wrapper for AAI vserver named-query response. This is initialized in a lazy fashion.
137      */
138     private AaiNqResponseWrapper nqVserverResponse = null;
139
140     private static Collection<String> requiredAAIKeys = new ArrayList<>();
141
142     static {
143         requiredAAIKeys.add("AICVServerSelfLink");
144         requiredAAIKeys.add("AICIdentity");
145         requiredAAIKeys.add("is_closed_loop_disabled");
146         requiredAAIKeys.add(VM_NAME);
147     }
148
149     /**
150      * Constructs the object.
151      *
152      * @param closedLoopControlName name of the control loop
153      * @param requestId ID of the request with which this manager is associated
154      */
155     public ControlLoopEventManager(String closedLoopControlName, UUID requestId) {
156         this.closedLoopControlName = closedLoopControlName;
157         this.requestId = requestId;
158     }
159
160     public String getClosedLoopControlName() {
161         return closedLoopControlName;
162     }
163
164     public String getControlLoopResult() {
165         return controlLoopResult;
166     }
167
168     public void setControlLoopResult(String controlLoopResult) {
169         this.controlLoopResult = controlLoopResult;
170     }
171
172     public Integer getNumOnsets() {
173         return numOnsets;
174     }
175
176     public void setNumOnsets(Integer numOnsets) {
177         this.numOnsets = numOnsets;
178     }
179
180     public Integer getNumAbatements() {
181         return numAbatements;
182     }
183
184     public void setNumAbatements(Integer numAbatements) {
185         this.numAbatements = numAbatements;
186     }
187
188     public boolean isActivated() {
189         return isActivated;
190     }
191
192     public void setActivated(boolean isActivated) {
193         this.isActivated = isActivated;
194     }
195
196     public boolean useTargetLock() {
197         return useTargetLock();
198     }
199
200     public void setUseTargetLock(boolean useTargetLock) {
201         this.useTargetLock = useTargetLock;
202     }
203
204     public VirtualControlLoopEvent getOnsetEvent() {
205         return this.onset;
206     }
207
208     public VirtualControlLoopEvent getAbatementEvent() {
209         return this.abatement;
210     }
211
212     public ControlLoopProcessor getProcessor() {
213         return this.processor;
214     }
215
216     public UUID getRequestId() {
217         return requestId;
218     }
219
220     /**
221      * Activate a control loop event.
222      *
223      * @param event the event
224      * @return the VirtualControlLoopNotification
225      */
226     public VirtualControlLoopNotification activate(VirtualControlLoopEvent event) {
227         VirtualControlLoopNotification notification = new VirtualControlLoopNotification(event);
228         try {
229             //
230             // This method should ONLY be called ONCE
231             //
232             if (this.isActivated) {
233                 throw new ControlLoopException("ControlLoopEventManager has already been activated.");
234             }
235             //
236             // Syntax check the event
237             //
238             checkEventSyntax(event);
239
240             //
241             // At this point we are good to go with this event
242             //
243             this.onset = event;
244             this.numOnsets = 1;
245             //
246             notification.setNotification(ControlLoopNotificationType.ACTIVE);
247             //
248             // Set ourselves as active
249             //
250             this.isActivated = true;
251         } catch (ControlLoopException e) {
252             logger.error("{}: activate by event threw: ", this, e);
253             notification.setNotification(ControlLoopNotificationType.REJECTED);
254             notification.setMessage(e.getMessage());
255         }
256         return notification;
257     }
258
259     /**
260      * Activate a control loop event.
261      *
262      * @param yamlSpecification the yaml specification
263      * @param event the event
264      * @return the VirtualControlLoopNotification
265      */
266     public VirtualControlLoopNotification activate(String yamlSpecification, VirtualControlLoopEvent event) {
267         VirtualControlLoopNotification notification = new VirtualControlLoopNotification(event);
268         try {
269             //
270             // This method should ONLY be called ONCE
271             //
272             if (this.isActivated) {
273                 throw new ControlLoopException("ControlLoopEventManager has already been activated.");
274             }
275             //
276             // Syntax check the event
277             //
278             checkEventSyntax(event);
279
280             //
281             // Check the YAML
282             //
283             if (yamlSpecification == null || yamlSpecification.length() < 1) {
284                 throw new ControlLoopException("yaml specification is null or 0 length");
285             }
286         } catch (ControlLoopException e) {
287             logger.error("{}: activate by YAML specification and event threw: ", this, e);
288             notification.setNotification(ControlLoopNotificationType.REJECTED);
289             notification.setMessage(e.getMessage());
290             return notification;
291         }
292
293         String decodedYaml = null;
294         try {
295             decodedYaml = URLDecoder.decode(yamlSpecification, "UTF-8");
296             if (decodedYaml != null && decodedYaml.length() > 0) {
297                 yamlSpecification = decodedYaml;
298             }
299         } catch (UnsupportedEncodingException e) {
300             logger.error("{}: YAML decode in activate by YAML specification and event threw: ", this, e);
301             notification.setNotification(ControlLoopNotificationType.REJECTED);
302             notification.setMessage(e.getMessage());
303             return notification;
304         }
305
306         try {
307             //
308             // Parse the YAML specification
309             //
310             this.processor = new ControlLoopProcessor(yamlSpecification);
311             //
312             // At this point we are good to go with this event
313             //
314             this.onset = event;
315             this.numOnsets = 1;
316             //
317             //
318             //
319             notification.setNotification(ControlLoopNotificationType.ACTIVE);
320             //
321             // Set ourselves as active
322             //
323             this.isActivated = true;
324         } catch (ControlLoopException e) {
325             logger.error("{}: activate by YAML specification and event threw: ", this, e);
326             notification.setNotification(ControlLoopNotificationType.REJECTED);
327             notification.setMessage(e.getMessage());
328         }
329         return notification;
330     }
331
332     /**
333      * Check if the control loop is final.
334      *
335      * @return a VirtualControlLoopNotification if the control loop is final, otherwise <code>null</code> is returned
336      * @throws ControlLoopException if an error occurs
337      */
338     public VirtualControlLoopNotification isControlLoopFinal() throws ControlLoopException {
339         validateFinalControlLoop();
340         //
341         // Ok, start creating the notification
342         //
343         VirtualControlLoopNotification notification = new VirtualControlLoopNotification(this.onset);
344         //
345         // Check if the overall control loop has timed out
346         //
347         if (this.isControlLoopTimedOut()) {
348             //
349             // Yes we have timed out
350             //
351             notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE);
352             notification.setMessage("Control Loop timed out");
353             notification.getHistory().addAll(this.controlLoopHistory);
354             return notification;
355         }
356         //
357         // Check if the current policy is Final
358         //
359         FinalResult result = this.processor.checkIsCurrentPolicyFinal();
360         if (result == null) {
361             //
362             // we are not at a final result
363             //
364             return null;
365         }
366
367         switch (result) {
368             case FINAL_FAILURE_EXCEPTION:
369                 notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE);
370                 notification.setMessage("Exception in processing closed loop");
371                 break;
372             case FINAL_FAILURE:
373             case FINAL_FAILURE_RETRIES:
374             case FINAL_FAILURE_TIMEOUT:
375             case FINAL_FAILURE_GUARD:
376                 notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE);
377                 break;
378             case FINAL_OPENLOOP:
379                 notification.setNotification(ControlLoopNotificationType.FINAL_OPENLOOP);
380                 break;
381             case FINAL_SUCCESS:
382                 notification.setNotification(ControlLoopNotificationType.FINAL_SUCCESS);
383                 break;
384             default:
385                 return null;
386         }
387         //
388         // Be sure to add all the history
389         //
390         notification.getHistory().addAll(this.controlLoopHistory);
391         return notification;
392     }
393
394     private void validateFinalControlLoop() throws ControlLoopException {
395         //
396         // Check if they activated us
397         //
398         if (!this.isActivated) {
399             throw new ControlLoopException("ControlLoopEventManager MUST be activated first.");
400         }
401         //
402         // Make sure we are expecting this call.
403         //
404         if (this.onset == null) {
405             throw new ControlLoopException("No onset event for ControlLoopEventManager.");
406         }
407     }
408
409     /**
410      * Process the control loop.
411      *
412      * @return a ControlLoopOperationManager
413      * @throws ControlLoopException if an error occurs
414      */
415     public ControlLoopOperationManager processControlLoop() throws ControlLoopException {
416         validateFinalControlLoop();
417         //
418         // Is there a current operation?
419         //
420         if (this.currentOperation != null) {
421             //
422             // Throw an exception, or simply return the current operation?
423             //
424             throw new ControlLoopException("Already working an Operation, do not call this method.");
425         }
426         //
427         // Ensure we are not FINAL
428         //
429         VirtualControlLoopNotification notification = this.isControlLoopFinal();
430         if (notification != null) {
431             //
432             // This is weird, we require them to call the isControlLoopFinal() method first
433             //
434             // We should really abstract this and avoid throwing an exception, because it really
435             // isn't an exception.
436             //
437             throw new ControlLoopException("Control Loop is in FINAL state, do not call this method.");
438         }
439         //
440         // Not final so get the policy that needs to be worked on.
441         //
442         Policy policy = this.processor.getCurrentPolicy();
443         if (policy == null) {
444             throw new ControlLoopException("ControlLoopEventManager: processor came upon null Policy.");
445         }
446         //
447         // And setup an operation
448         //
449         this.lastOperationManager = this.currentOperation;
450         this.currentOperation = new ControlLoopOperationManager(this.onset, policy, this);
451         //
452         // Return it
453         //
454         return this.currentOperation;
455     }
456
457     /**
458      * Finish an operation.
459      *
460      * @param operation the operation
461      */
462     public void finishOperation(ControlLoopOperationManager operation) throws ControlLoopException {
463         //
464         // Verify we have a current operation
465         //
466         if (this.currentOperation != null) {
467             //
468             // Validate they are finishing the current operation
469             // PLD - this is simply comparing the policy. Do we want to equals the whole object?
470             //
471             if (this.currentOperation.policy.equals(operation.policy)) {
472                 logger.debug("Finishing {} result is {}", this.currentOperation.policy.getRecipe(),
473                         this.currentOperation.getOperationResult());
474                 //
475                 // Save history
476                 //
477                 this.controlLoopHistory.addAll(this.currentOperation.getHistory());
478                 //
479                 // Move to the next Policy
480                 //
481                 this.processor.nextPolicyForResult(this.currentOperation.getOperationResult());
482                 //
483                 // Just null this out
484                 //
485                 this.lastOperationManager = this.currentOperation;
486                 this.currentOperation = null;
487
488                 //
489                 // Don't release the lock - it may be re-used by the next operation
490                 //
491
492                 return;
493             }
494             logger.debug("Cannot finish current operation {} does not match given operation {}",
495                     this.currentOperation.policy, operation.policy);
496             return;
497         }
498         throw new ControlLoopException("No operation to finish.");
499     }
500
501     /**
502      * Obtain a lock for the current operation.
503      *
504      * @param callback call-back to be invoked when the lock state changes
505      * @return a pair containing the old lock and the new lock, either of which may be null
506      * @throws ControlLoopException if an error occurs
507      */
508     public synchronized Pair<Lock, Lock> lockCurrentOperation(LockCallback callback) throws ControlLoopException {
509         //
510         // Sanity check
511         //
512         if (this.currentOperation == null) {
513             throw new ControlLoopException("Do not have a current operation.");
514         }
515
516         //
517         // Release the old lock if it's for a different resource.
518         //
519         Lock oldLock = null;
520         if (this.targetLock != null
521                         && !this.targetLock.getResourceId().equals(this.currentOperation.getTargetEntity())) {
522             logger.debug("{}: different resource - releasing old lock", getClosedLoopControlName());
523             oldLock = this.targetLock;
524             this.targetLock = null;
525         }
526
527         // keep the lock a little longer than the operation, including retries
528         int optimeout = Math.max(1, this.currentOperation.getOperationTimeout());
529         int nattempts = 1 + Math.max(0, this.currentOperation.getMaxRetries());
530         int holdSec = optimeout * nattempts + ADDITIONAL_LOCK_SEC;
531
532         //
533         // Have we acquired it already?
534         //
535         if (this.targetLock != null) {
536             // we have the lock - just extend it
537             this.targetLock.extend(holdSec, callback);
538             return new Pair<>(oldLock, null);
539
540         } else if (this.useTargetLock) {
541             this.targetLock = createRealLock(this.currentOperation.getTargetEntity(), this.onset.getRequestId(),
542                             holdSec, callback);
543             return new Pair<>(oldLock, this.targetLock);
544
545         } else {
546             // Not using target locks - create a lock w/o actually locking.
547             logger.debug("{}: not using target locking; using pseudo locks", getClosedLoopControlName());
548             this.targetLock = createPseudoLock(this.currentOperation.getTargetEntity(), this.onset.getRequestId(),
549                             holdSec, callback);
550
551             // Note: no need to invoke callback, as the lock is already ACTIVE
552
553             return new Pair<>(oldLock, this.targetLock);
554         }
555     }
556
557     /**
558      * Releases the lock for the current operation, deleting it from working memory.
559      *
560      * @return the lock, if the operation was locked, {@code null} otherwise
561      */
562     public synchronized Lock unlockCurrentOperation() {
563         if (this.targetLock == null) {
564             return null;
565         }
566
567         Lock lock = this.targetLock;
568         this.targetLock = null;
569
570         lock.free();
571
572         return lock;
573     }
574
575     public enum NewEventStatus {
576         FIRST_ONSET, SUBSEQUENT_ONSET, FIRST_ABATEMENT, SUBSEQUENT_ABATEMENT, SYNTAX_ERROR;
577     }
578
579     /**
580      * An event onset/abatement.
581      *
582      * @param event the event
583      * @return the status
584      * @throws AaiException if an error occurs retrieving information from A&AI
585      */
586     public NewEventStatus onNewEvent(VirtualControlLoopEvent event) throws AaiException {
587         try {
588             this.checkEventSyntax(event);
589             if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ONSET) {
590                 //
591                 // Check if this is our original ONSET
592                 //
593                 if (event.equals(this.onset)) {
594                     //
595                     // Query A&AI if needed
596                     //
597                     queryAai(event);
598
599                     //
600                     // DO NOT retract it
601                     //
602                     return NewEventStatus.FIRST_ONSET;
603                 }
604                 //
605                 // Log that we got an onset
606                 //
607                 this.numOnsets++;
608                 return NewEventStatus.SUBSEQUENT_ONSET;
609             } else if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ABATED) {
610                 //
611                 // Have we already got an abatement?
612                 //
613                 if (this.abatement == null) {
614                     //
615                     // Save this
616                     //
617                     this.abatement = event;
618                     //
619                     // Keep track that we received another
620                     //
621                     this.numAbatements++;
622                     //
623                     //
624                     //
625                     return NewEventStatus.FIRST_ABATEMENT;
626                 } else {
627                     //
628                     // Keep track that we received another
629                     //
630                     this.numAbatements++;
631                     //
632                     //
633                     //
634                     return NewEventStatus.SUBSEQUENT_ABATEMENT;
635                 }
636             }
637         } catch (ControlLoopException e) {
638             logger.error("{}: onNewEvent threw: ", this, e);
639         }
640         return NewEventStatus.SYNTAX_ERROR;
641     }
642
643
644     /**
645      * Commit the abatement to the history database.
646      *
647      * @param message the abatement message
648      * @param outcome the abatement outcome
649      */
650     public void commitAbatement(String message, String outcome) {
651         if (this.lastOperationManager == null) {
652             logger.error("{}: commitAbatement: no operation manager", this);
653             return;
654         }
655         try {
656             this.lastOperationManager.commitAbatement(message, outcome);
657         } catch (NoSuchElementException e) {
658             logger.error("{}: commitAbatement threw an exception ", this, e);
659         }
660     }
661
662
663     /**
664      * Set the control loop time out.
665      *
666      * @return a VirtualControlLoopNotification
667      */
668     public VirtualControlLoopNotification setControlLoopTimedOut() {
669         this.controlLoopTimedOut = FinalResult.FINAL_FAILURE_TIMEOUT;
670         VirtualControlLoopNotification notification = new VirtualControlLoopNotification(this.onset);
671         notification.setNotification(ControlLoopNotificationType.FINAL_FAILURE);
672         notification.setMessage("Control Loop timed out");
673         notification.getHistory().addAll(this.controlLoopHistory);
674         return notification;
675     }
676
677     public boolean isControlLoopTimedOut() {
678         return (this.controlLoopTimedOut == FinalResult.FINAL_FAILURE_TIMEOUT);
679     }
680
681     /**
682      * Get the control loop timeout.
683      *
684      * @param defaultTimeout the default timeout
685      * @return the timeout
686      */
687     public int getControlLoopTimeout(Integer defaultTimeout) {
688         if (this.processor != null && this.processor.getControlLoop() != null) {
689             Integer timeout = this.processor.getControlLoop().getTimeout();
690             if (timeout != null && timeout > 0) {
691                 return timeout;
692             }
693         }
694         if (defaultTimeout != null) {
695             return defaultTimeout;
696         }
697         return 0;
698     }
699
700     public AaiGetVnfResponse getVnfResponse() {
701         return vnfResponse;
702     }
703
704     public AaiGetVserverResponse getVserverResponse() {
705         return vserverResponse;
706     }
707
708     /**
709      * Check an event syntax.
710      *
711      * @param event the event syntax
712      * @throws ControlLoopException if an error occurs
713      */
714     public void checkEventSyntax(VirtualControlLoopEvent event) throws ControlLoopException {
715         validateStatus(event);
716         if (StringUtils.isBlank(event.getClosedLoopControlName())) {
717             throw new ControlLoopException("No control loop name");
718         }
719         if (event.getRequestId() == null) {
720             throw new ControlLoopException("No request ID");
721         }
722         if (event.getClosedLoopEventStatus() == ControlLoopEventStatus.ABATED) {
723             return;
724         }
725         if (StringUtils.isBlank(event.getTarget())) {
726             throw new ControlLoopException("No target field");
727         } else if (!VALID_TARGETS.contains(event.getTarget().toLowerCase())) {
728             throw new ControlLoopException("target field invalid - expecting VM_NAME or VNF_NAME");
729         }
730         validateAaiData(event);
731     }
732
733     private void validateStatus(VirtualControlLoopEvent event) throws ControlLoopException {
734         if (event.getClosedLoopEventStatus() == null
735                 || (event.getClosedLoopEventStatus() != ControlLoopEventStatus.ONSET
736                         && event.getClosedLoopEventStatus() != ControlLoopEventStatus.ABATED)) {
737             throw new ControlLoopException("Invalid value in closedLoopEventStatus");
738         }
739     }
740
741     private void validateAaiData(VirtualControlLoopEvent event) throws ControlLoopException {
742         Map<String, String> eventAai = event.getAai();
743         if (eventAai == null) {
744             throw new ControlLoopException("AAI is null");
745         }
746         switch (event.getTargetType()) {
747             case VM:
748             case VNF:
749                 validateAaiVmVnfData(eventAai);
750                 return;
751             case PNF:
752                 validateAaiPnfData(eventAai);
753                 return;
754             default:
755                 throw new ControlLoopException("The target type is not supported");
756         }
757     }
758
759     private void validateAaiVmVnfData(Map<String, String> eventAai) throws ControlLoopException {
760         if (eventAai.get(GENERIC_VNF_VNF_ID) == null && eventAai.get(VSERVER_VSERVER_NAME) == null
761                     && eventAai.get(GENERIC_VNF_VNF_NAME) == null) {
762             throw new ControlLoopException(
763                     "generic-vnf.vnf-id or generic-vnf.vnf-name or vserver.vserver-name information missing");
764         }
765     }
766
767     private void validateAaiPnfData(Map<String, String> eventAai) throws ControlLoopException {
768         if (eventAai.get(PNF_NAME) == null) {
769             throw new ControlLoopException("AAI PNF object key pnf-name is missing");
770         }
771     }
772
773     /**
774      * Query A&AI for an event.
775      *
776      * @param event the event
777      * @throws AaiException if an error occurs retrieving information from A&AI
778      */
779     public void queryAai(VirtualControlLoopEvent event) throws AaiException {
780
781         Map<String, String> aai = event.getAai();
782
783         if (alreadyHaveData(event, aai)) {
784             return;
785         }
786
787         if (vnfResponse != null || vserverResponse != null) {
788             // query has already been performed
789             return;
790         }
791
792         try {
793             if (aai.containsKey(GENERIC_VNF_VNF_ID) || aai.containsKey(GENERIC_VNF_VNF_NAME)) {
794                 vnfResponse = getAaiVnfInfo(event);
795                 processVnfResponse(vnfResponse, aai.containsKey(GENERIC_VNF_VNF_ID));
796             } else if (aai.containsKey(VSERVER_VSERVER_NAME)) {
797                 vserverResponse = getAaiVserverInfo(event);
798                 processVServerResponse(vserverResponse);
799             }
800         } catch (AaiException e) {
801             logger.error(QUERY_AAI_ERROR_MSG, e);
802             throw e;
803         } catch (Exception e) {
804             logger.error(QUERY_AAI_ERROR_MSG, e);
805             throw new AaiException(QUERY_AAI_ERROR_MSG + e.toString());
806         }
807     }
808
809     private boolean alreadyHaveData(VirtualControlLoopEvent event, Map<String, String> aai) throws AaiException {
810         if (aai.containsKey(VSERVER_IS_CLOSED_LOOP_DISABLED) || aai.containsKey(GENERIC_VNF_IS_CLOSED_LOOP_DISABLED)
811                     || aai.containsKey(PNF_IS_IN_MAINT)) {
812
813             if (isClosedLoopDisabled(event)) {
814                 throw new AaiException(
815                         "is-closed-loop-disabled is set to true on VServer or VNF or in-maint is set to true for PNF");
816             }
817
818             if (isProvStatusInactive(event)) {
819                 throw new AaiException("prov-status is not ACTIVE on VServer or VNF or PNF");
820             }
821
822             // no need to query, as we already have the data
823             return true;
824         }
825
826         return false;
827     }
828
829     /**
830      * Process a response from A&AI for a VNF.
831      *
832      * @param aaiResponse the response from A&AI
833      * @param queryByVnfId <code>true</code> if the query was based on vnf-id, <code>false</code> if the query was based
834      *        on vnf-name
835      * @throws AaiException if an error occurs processing the response
836      */
837     private static void processVnfResponse(AaiGetVnfResponse aaiResponse, boolean queryByVnfId) throws AaiException {
838         String queryTypeString = (queryByVnfId ? "vnf-id" : "vnf-name");
839
840         if (aaiResponse == null) {
841             throw new AaiException("AAI Response is null (query by " + queryTypeString + ")");
842         }
843         if (aaiResponse.getRequestError() != null) {
844             throw new AaiException("AAI Responded with a request error (query by " + queryTypeString + ")");
845         }
846
847         if (aaiResponse.isClosedLoopDisabled()) {
848             throw new AaiException("is-closed-loop-disabled is set to true (query by " + queryTypeString + ")");
849         }
850
851         if (!PROV_STATUS_ACTIVE.equals(aaiResponse.getProvStatus())) {
852             throw new AaiException("prov-status is not ACTIVE (query by " + queryTypeString + ")");
853         }
854     }
855
856     /**
857      * Process a response from A&AI for a VServer.
858      *
859      * @param aaiResponse the response from A&AI
860      * @throws AaiException if an error occurs processing the response
861      */
862     private static void processVServerResponse(AaiGetVserverResponse aaiResponse) throws AaiException {
863         if (aaiResponse == null) {
864             throw new AaiException("AAI Response is null (query by vserver-name)");
865         }
866         if (aaiResponse.getRequestError() != null) {
867             throw new AaiException("AAI Responded with a request error (query by vserver-name)");
868         }
869
870         List<AaiNqVServer> lst = aaiResponse.getVserver();
871         if (lst.isEmpty()) {
872             return;
873         }
874
875         AaiNqVServer svr = lst.get(0);
876         if (svr.getIsClosedLoopDisabled()) {
877             throw new AaiException("is-closed-loop-disabled is set to true (query by vserver-name)");
878         }
879
880         if (!PROV_STATUS_ACTIVE.equals(svr.getProvStatus())) {
881             throw new AaiException("prov-status is not ACTIVE (query by vserver-name)");
882         }
883     }
884
885     /**
886      * Is closed loop disabled for an event.
887      *
888      * @param event the event
889      * @return <code>true</code> if the control loop is disabled, <code>false</code> otherwise
890      */
891     public static boolean isClosedLoopDisabled(VirtualControlLoopEvent event) {
892         Map<String, String> aai = event.getAai();
893         return (isAaiTrue(aai.get(VSERVER_IS_CLOSED_LOOP_DISABLED))
894                 || isAaiTrue(aai.get(GENERIC_VNF_IS_CLOSED_LOOP_DISABLED))
895                 || isAaiTrue(aai.get(PNF_IS_IN_MAINT)));
896     }
897
898     /**
899      * Does provisioning status, for an event, have a value other than ACTIVE.
900      *
901      * @param event the event
902      * @return {@code true} if the provisioning status is neither ACTIVE nor {@code null}, {@code false} otherwise
903      */
904     protected static boolean isProvStatusInactive(VirtualControlLoopEvent event) {
905         Map<String, String> aai = event.getAai();
906         return (!PROV_STATUS_ACTIVE.equals(aai.getOrDefault(VSERVER_PROV_STATUS, PROV_STATUS_ACTIVE))
907                 || !PROV_STATUS_ACTIVE.equals(aai.getOrDefault(GENERIC_VNF_PROV_STATUS, PROV_STATUS_ACTIVE)));
908     }
909
910     /**
911      * Determines the boolean value represented by the given AAI field value.
912      *
913      * @param aaiValue value to be examined
914      * @return the boolean value represented by the field value, or {@code false} if the value is {@code null}
915      */
916     protected static boolean isAaiTrue(String aaiValue) {
917         return ("true".equalsIgnoreCase(aaiValue) || "T".equalsIgnoreCase(aaiValue) || "yes".equalsIgnoreCase(aaiValue)
918                 || "Y".equalsIgnoreCase(aaiValue));
919     }
920
921     /**
922      * Get the A&AI VService information for an event.
923      *
924      * @param event the event
925      * @return a AaiGetVserverResponse
926      * @throws ControlLoopException if an error occurs
927      */
928     public static AaiGetVserverResponse getAaiVserverInfo(VirtualControlLoopEvent event) throws ControlLoopException {
929         UUID requestId = event.getRequestId();
930         AaiGetVserverResponse response = null;
931         String vserverName = event.getAai().get(VSERVER_VSERVER_NAME);
932
933         try {
934             if (vserverName != null) {
935                 String aaiHostUrl = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_URL);
936                 String aaiUser = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_USERNAME_PROPERTY);
937                 String aaiPassword = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_PASS_PROPERTY);
938                 String aaiGetQueryByVserver = "/aai/v11/nodes/vservers?vserver-name=";
939                 String url = aaiHostUrl + aaiGetQueryByVserver;
940                 logger.info("AAI Host URL by VServer: {}", url);
941                 response = new AaiManager(new RestManager()).getQueryByVserverName(url, aaiUser, aaiPassword, requestId,
942                         vserverName);
943             }
944         } catch (Exception e) {
945             logger.error("getAaiVserverInfo exception: ", e);
946             throw new ControlLoopException("Exception in getAaiVserverInfo: ", e);
947         }
948
949         return response;
950     }
951
952     /**
953      * Get A&AI VNF information for an event.
954      *
955      * @param event the event
956      * @return a AaiGetVnfResponse
957      * @throws ControlLoopException if an error occurs
958      */
959     public static AaiGetVnfResponse getAaiVnfInfo(VirtualControlLoopEvent event) throws ControlLoopException {
960         UUID requestId = event.getRequestId();
961         AaiGetVnfResponse response = null;
962         String vnfName = event.getAai().get(GENERIC_VNF_VNF_NAME);
963         String vnfId = event.getAai().get(GENERIC_VNF_VNF_ID);
964
965         String aaiHostUrl = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_URL);
966         String aaiUser = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_USERNAME_PROPERTY);
967         String aaiPassword = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_PASS_PROPERTY);
968
969         try {
970             if (vnfName != null) {
971                 String aaiGetQueryByVnfName = "/aai/v11/network/generic-vnfs/generic-vnf?vnf-name=";
972                 String url = aaiHostUrl + aaiGetQueryByVnfName;
973                 logger.info("AAI Host URL by VNF name: {}", url);
974                 response = new AaiManager(new RestManager()).getQueryByVnfName(url, aaiUser, aaiPassword, requestId,
975                         vnfName);
976             } else if (vnfId != null) {
977                 String aaiGetQueryByVnfId = "/aai/v11/network/generic-vnfs/generic-vnf/";
978                 String url = aaiHostUrl + aaiGetQueryByVnfId;
979                 logger.info("AAI Host URL by VNF ID: {}", url);
980                 response =
981                         new AaiManager(new RestManager()).getQueryByVnfId(url, aaiUser, aaiPassword, requestId, vnfId);
982             }
983         } catch (Exception e) {
984             logger.error("getAaiVnfInfo exception: ", e);
985             throw new ControlLoopException("Exception in getAaiVnfInfo: ", e);
986         }
987
988         return response;
989     }
990
991     /**
992      * Gets the output from the AAI vserver named-query, using the cache, if appropriate.
993      *
994      * @return output from the AAI vserver named-query
995      */
996     public AaiNqResponseWrapper getNqVserverFromAai() {
997         if (nqVserverResponse != null) {
998             // already queried
999             return nqVserverResponse;
1000         }
1001
1002         String vserverName = onset.getAai().get(VSERVER_VSERVER_NAME);
1003         if (vserverName == null) {
1004             logger.warn("Missing vserver-name for AAI request {}", onset.getRequestId());
1005             return null;
1006         }
1007         AaiNqRequest aaiNqRequest = getAaiNqRequest(vserverName);
1008
1009         if (logger.isDebugEnabled()) {
1010             logger.debug("AAI Request sent: {}", Serialization.gsonPretty.toJson(aaiNqRequest));
1011         }
1012
1013         AaiNqResponse aaiNqResponse = new AaiManager(new RestManager()).postQuery(getPeManagerEnvProperty(AAI_URL),
1014                 getPeManagerEnvProperty(AAI_USERNAME_PROPERTY), getPeManagerEnvProperty(AAI_PASS_PROPERTY),
1015                 aaiNqRequest, onset.getRequestId());
1016
1017         // Check AAI response
1018         if (aaiNqResponse == null) {
1019             logger.warn("No response received from AAI for request {}", aaiNqRequest);
1020             return null;
1021         }
1022
1023         // Create AAINQResponseWrapper
1024         nqVserverResponse = new AaiNqResponseWrapper(onset.getRequestId(), aaiNqResponse);
1025
1026         if (logger.isDebugEnabled()) {
1027             logger.debug("AAI Named Query Response: ");
1028             logger.debug(Serialization.gsonPretty.toJson(nqVserverResponse.getAaiNqResponse()));
1029         }
1030
1031         return nqVserverResponse;
1032     }
1033
1034     /**
1035      * Gets an AAI Named Query Request object.
1036      *
1037      * @param vserverName vserver name.
1038      * @return the AAI Named Query Request object.
1039      */
1040     public static AaiNqRequest getAaiNqRequest(String vserverName) {
1041         // create AAI named-query request with UUID started with ""
1042         AaiNqRequest aaiNqRequest = new AaiNqRequest();
1043         AaiNqQueryParameters aaiNqQueryParam = new AaiNqQueryParameters();
1044         AaiNqNamedQuery aaiNqNamedQuery = new AaiNqNamedQuery();
1045         final AaiNqInstanceFilters aaiNqInstanceFilter = new AaiNqInstanceFilters();
1046
1047         // queryParameters
1048         aaiNqNamedQuery.setNamedQueryUuid(UUID.fromString("4ff56a54-9e3f-46b7-a337-07a1d3c6b469"));
1049         aaiNqQueryParam.setNamedQuery(aaiNqNamedQuery);
1050         aaiNqRequest.setQueryParameters(aaiNqQueryParam);
1051         //
1052         // instanceFilters
1053         //
1054         Map<String, Map<String, String>> aaiNqInstanceFilterMap = new HashMap<>();
1055         Map<String, String> aaiNqInstanceFilterMapItem = new HashMap<>();
1056         aaiNqInstanceFilterMapItem.put("vserver-name", vserverName);
1057         aaiNqInstanceFilterMap.put("vserver", aaiNqInstanceFilterMapItem);
1058         aaiNqInstanceFilter.getInstanceFilter().add(aaiNqInstanceFilterMap);
1059         aaiNqRequest.setInstanceFilters(aaiNqInstanceFilter);
1060         return aaiNqRequest;
1061     }
1062
1063     /**
1064      * This method reads and validates environmental properties coming from the policy engine. Null properties cause an
1065      * {@link IllegalArgumentException} runtime exception to be thrown
1066      *
1067      * @param enginePropertyName the name of the parameter to retrieve
1068      * @return the property value
1069      */
1070     private static String getPeManagerEnvProperty(String enginePropertyName) {
1071         String enginePropertyValue = PolicyEngineConstants.getManager().getEnvironmentProperty(enginePropertyName);
1072         if (enginePropertyValue == null) {
1073             throw new IllegalArgumentException("The value of policy engine manager environment property \""
1074                     + enginePropertyName + "\" may not be null");
1075         }
1076         return enginePropertyValue;
1077     }
1078
1079     @Override
1080     public String toString() {
1081         return "ControlLoopEventManager [closedLoopControlName=" + closedLoopControlName + ", requestId=" + requestId
1082                 + ", processor=" + processor + ", onset=" + (onset != null ? onset.getRequestId() : "null")
1083                 + ", numOnsets=" + numOnsets + ", numAbatements=" + numAbatements + ", isActivated=" + isActivated
1084                 + ", currentOperation=" + currentOperation + ", targetLock=" + targetLock + "]";
1085     }
1086
1087     /**
1088      * This function calls Aai Custom Query and responds with the AaiCqResponse.
1089      *
1090      * @param event input event
1091      * @return AaiCqResponse Response from Aai for custom query. Can not be null.
1092      * @throws AaiException if error occurs
1093      */
1094     public AaiCqResponse getCqResponse(VirtualControlLoopEvent event) throws AaiException {
1095
1096         Map<String, String> aai = event.getAai();
1097
1098         if (aai.containsKey(VSERVER_IS_CLOSED_LOOP_DISABLED) || aai.containsKey(GENERIC_VNF_IS_CLOSED_LOOP_DISABLED)) {
1099
1100             if (isClosedLoopDisabled(event)) {
1101                 throw new AaiException("is-closed-loop-disabled is set to true on VServer or VNF");
1102             }
1103
1104             if (isProvStatusInactive(event)) {
1105                 throw new AaiException("prov-status is not ACTIVE on VServer or VNF");
1106             }
1107         }
1108
1109         if (!aai.containsKey(VSERVER_VSERVER_NAME)) {
1110             throw new AaiException("Vserver name is missing");
1111         }
1112
1113         UUID reqId = event.getRequestId();
1114         AaiCqResponse response = null;
1115         String vserverId = event.getAai().get(VSERVER_VSERVER_NAME);
1116
1117         String aaiHostUrl = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_URL);
1118         String aaiUser = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_USERNAME_PROPERTY);
1119         String aaiPassword = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_PASS_PROPERTY);
1120
1121         response = new AaiManager(new RestManager()).getCustomQueryResponse(aaiHostUrl, aaiUser, aaiPassword, reqId,
1122                 vserverId);
1123
1124         if (response == null) {
1125             throw new AaiException("Aai response is undefined");
1126         }
1127
1128         return response;
1129
1130     }
1131
1132     /**
1133      * Get the specified pnf data from aai.
1134      * @param event the event containing pnf id.
1135      * @return pnf key value data.
1136      * @throws AaiException if an aai error occurs.
1137      */
1138     public Map<String, String> getPnf(VirtualControlLoopEvent event) throws AaiException {
1139         Map<String, String> aai = event.getAai();
1140
1141         if (!aai.containsKey(PNF_NAME)) {
1142             throw new AaiException("Missing unique identifier for PNF AAI object in the event.");
1143         }
1144
1145         UUID reqId = event.getRequestId();
1146         String pnfName = event.getAai().get(PNF_NAME);
1147         String aaiHostUrl = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_URL);
1148         String aaiUser = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_USERNAME_PROPERTY);
1149         String aaiPassword = PolicyEngineConstants.getManager().getEnvironmentProperty(AAI_PASS_PROPERTY);
1150
1151         Map<String, String> pnfParams =
1152                 new AaiManager(new RestManager()).getPnf(aaiHostUrl, aaiUser, aaiPassword, reqId, pnfName);
1153
1154         if (pnfParams == null) {
1155             throw new AaiException("Aai response is undefined");
1156         }
1157         return pnfParams;
1158     }
1159
1160
1161     // the following methods may be overridden by junit tests
1162
1163     protected Lock createRealLock(String targetEntity, UUID requestId, int holdSec, LockCallback callback) {
1164         return PolicyEngineConstants.getManager().createLock(targetEntity, requestId.toString(), holdSec, callback,
1165                         false);
1166     }
1167
1168     // note: the "callback" is required, because it will be invoked when lock.extend() is
1169     // invoked
1170     protected Lock createPseudoLock(String targetEntity, UUID requestId, int holdSec, LockCallback callback) {
1171         return new LockImpl(LockState.ACTIVE, targetEntity, requestId.toString(), holdSec, callback);
1172     }
1173 }