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