Adding missing tests
[vfc/nfvo/driver/vnfm/svnfm.git] / nokiav2 / driver / src / main / java / org / onap / vfc / nfvo / driver / vnfm / svnfm / nokia / vnfm / notification / LifecycleChangeNotificationManager.java
1 /*
2  * Copyright 2016-2017, Nokia Corporation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.notification;
17
18 import com.google.common.annotations.VisibleForTesting;
19 import com.google.common.collect.Ordering;
20 import com.google.gson.Gson;
21 import com.google.gson.JsonElement;
22 import com.google.gson.JsonObject;
23 import com.nokia.cbam.lcm.v32.ApiException;
24 import com.nokia.cbam.lcm.v32.api.OperationExecutionsApi;
25 import com.nokia.cbam.lcm.v32.api.VnfsApi;
26 import com.nokia.cbam.lcm.v32.model.*;
27 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.api.INotificationSender;
28 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.SystemFunctions;
29 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider;
30 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.DriverProperties;
31 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.ILifecycleChangeNotificationManager;
32 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.LifecycleManager;
33 import org.slf4j.Logger;
34 import org.springframework.beans.factory.annotation.Autowired;
35 import org.springframework.stereotype.Component;
36
37 import java.util.List;
38 import java.util.Optional;
39 import java.util.Set;
40
41 import static com.google.common.collect.Iterables.find;
42 import static com.google.common.collect.Iterables.tryFind;
43 import static com.google.common.collect.Sets.newConcurrentHashSet;
44 import static com.google.common.collect.Sets.newHashSet;
45 import static com.nokia.cbam.lcm.v32.model.OperationType.INSTANTIATE;
46 import static java.util.Optional.empty;
47 import static java.util.Optional.of;
48 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.buildFatalFailure;
49 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.childElement;
50 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCM_API_VERSION;
51 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCN_API_VERSION;
52 import static org.slf4j.LoggerFactory.getLogger;
53
54 /**
55  * Responsible for handling lifecycle change notifications from CBAM.
56  * The received LCNs are transformed into ONAP LCNs.
57  * The following CBAM LCNs are processed:
58  * - HEAL
59  * - INSTANTIATE
60  * - SCALE
61  * - TERMINATE
62  * The current limitations
63  * - if a LCN can not be be processed due to VNF having been deleted the problem is logged and CBAM is notified that
64  * the LCN has been processed (even if not in reality) because the signaling of failed LCN delivery blocks the delivery
65  * on all LCN deliveries. The consequence of this is that the information known by VF-C / A&AI may be inconsistent with
66  * reality (VNF having been deleted)
67  */
68 @Component
69 public class LifecycleChangeNotificationManager implements ILifecycleChangeNotificationManager {
70
71     public static final String PROBLEM = "All operations must return the { \"operationResult\" : { \"cbam_pre\" : [<fillMeOut>], \"cbam_post\" : [<fillMeOut>] } } structure";
72     /**
73      * Order the operations by start time (latest first)
74      */
75     public static final Ordering<OperationExecution> NEWEST_OPERATIONS_FIRST = new Ordering<OperationExecution>() {
76         @Override
77         public int compare(OperationExecution left, OperationExecution right) {
78             return right.getStartTime().toLocalDate().compareTo(left.getStartTime().toLocalDate());
79         }
80     };
81     /**
82      * < Separates the VNF id and the resource id within a VNF
83      */
84     private static final Set<OperationStatus> terminalStatus = newHashSet(OperationStatus.FINISHED, OperationStatus.FAILED);
85     private static Logger logger = getLogger(LifecycleChangeNotificationManager.class);
86
87     private final CbamRestApiProvider restApiProvider;
88     private final DriverProperties driverProperties;
89     private final INotificationSender notificationSender;
90     private Set<ProcessedNotification> processedNotifications = newConcurrentHashSet();
91
92     @Autowired
93     LifecycleChangeNotificationManager(CbamRestApiProvider restApiProvider, DriverProperties driverProperties, INotificationSender notificationSender) {
94         this.notificationSender = notificationSender;
95         this.driverProperties = driverProperties;
96         this.restApiProvider = restApiProvider;
97     }
98
99     /**
100      * @param status the status of the operation
101      * @return has the operation finished
102      */
103     public static boolean isTerminal(OperationStatus status) {
104         return terminalStatus.contains(status);
105     }
106
107     @VisibleForTesting
108     static OperationExecution findLastInstantiationBefore(List<OperationExecution> operationExecutions, OperationExecution currentOperation) {
109         return find(NEWEST_OPERATIONS_FIRST.sortedCopy(operationExecutions), (OperationExecution opex2) ->
110                 !opex2.getStartTime().isAfter(currentOperation.getStartTime())
111                         && INSTANTIATE.equals(opex2.getOperationType()));
112     }
113
114     @Override
115     public void handleLcn(VnfLifecycleChangeNotification receivedNotification) {
116         if (logger.isInfoEnabled()) {
117             logger.info("Received LCN: {}", new Gson().toJson(receivedNotification));
118         }
119         VnfsApi cbamLcmApi = restApiProvider.getCbamLcmApi(driverProperties.getVnfmId());
120         try {
121             List<VnfInfo> vnfs = cbamLcmApi.vnfsGet(NOKIA_LCM_API_VERSION);
122             com.google.common.base.Optional<VnfInfo> currentVnf = tryFind(vnfs, vnf -> vnf.getId().equals(receivedNotification.getVnfInstanceId()));
123             String vnfHeader = "The VNF with " + receivedNotification.getVnfInstanceId() + " identifier";
124             if (!currentVnf.isPresent()) {
125                 logger.warn(vnfHeader + " disappeared before being able to process the LCN");
126                 //swallow LCN
127                 return;
128             } else {
129                 VnfInfo vnf = cbamLcmApi.vnfsVnfInstanceIdGet(receivedNotification.getVnfInstanceId(), NOKIA_LCN_API_VERSION);
130                 com.google.common.base.Optional<VnfProperty> externalVnfmId = tryFind(vnf.getExtensions(), prop -> prop.getName().equals(LifecycleManager.EXTERNAL_VNFM_ID));
131                 if (!externalVnfmId.isPresent()) {
132                     logger.warn(vnfHeader + " is not a managed VNF");
133                     return;
134                 }
135                 if (!externalVnfmId.get().getValue().equals(driverProperties.getVnfmId())) {
136                     logger.warn(vnfHeader + " is not a managed by the VNFM with id " + externalVnfmId.get().getValue());
137                     return;
138                 }
139             }
140         } catch (Exception e) {
141             throw buildFatalFailure(logger, "Unable to list VNFs / query VNF", e);
142         }
143         OperationExecutionsApi cbamOperationExecutionApi = restApiProvider.getCbamOperationExecutionApi(driverProperties.getVnfmId());
144         try {
145             List<OperationExecution> operationExecutions = cbamLcmApi.vnfsVnfInstanceIdOperationExecutionsGet(receivedNotification.getVnfInstanceId(), NOKIA_LCM_API_VERSION);
146             OperationExecution operationExecution = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdGet(receivedNotification.getLifecycleOperationOccurrenceId(), NOKIA_LCM_API_VERSION);
147             OperationExecution closestInstantiationToOperation = findLastInstantiationBefore(operationExecutions, operationExecution);
148             String vimId = getVimId(cbamOperationExecutionApi, closestInstantiationToOperation);
149             notificationSender.processNotification(receivedNotification, operationExecution, buildAffectedCps(operationExecution), vimId);
150             if (isTerminationFinished(receivedNotification)) {
151                 //signal LifecycleManager to continue the deletion of the VNF
152                 processedNotifications.add(new ProcessedNotification(receivedNotification.getLifecycleOperationOccurrenceId(), receivedNotification.getStatus()));
153             }
154         } catch (ApiException e) {
155             throw buildFatalFailure(logger, "Unable to retrieve the current VNF " + receivedNotification.getVnfInstanceId(), e);
156         }
157     }
158
159     private boolean isTerminationFinished(VnfLifecycleChangeNotification receivedNotification) {
160         return OperationType.TERMINATE.equals(receivedNotification.getOperation()) && terminalStatus.contains(receivedNotification.getStatus());
161     }
162
163     private String getVimId(OperationExecutionsApi cbamOperationExecutionApi, OperationExecution closestInstantiationToOperation) {
164         try {
165             Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(closestInstantiationToOperation.getId(), NOKIA_LCM_API_VERSION);
166             return getVimId(operationParams);
167         } catch (Exception e) {
168             throw buildFatalFailure(logger, "Unable to detect last instantiation operation", e);
169         }
170     }
171
172     @Override
173     public void waitForTerminationToBeProcessed(String operationExecutionId) {
174         while (true) {
175             com.google.common.base.Optional<ProcessedNotification> notification = tryFind(processedNotifications, processedNotification -> processedNotification.getOperationExecutionId().equals(operationExecutionId));
176             if (notification.isPresent()) {
177                 processedNotifications.remove(notification.get());
178                 return;
179             }
180             SystemFunctions.systemFunctions().sleep(500);
181         }
182     }
183
184     private String getVimId(Object instantiationParameters) {
185         InstantiateVnfRequest request = new Gson().fromJson(new Gson().toJson(instantiationParameters), InstantiateVnfRequest.class);
186         return request.getVims().get(0).getId();
187     }
188
189     private Optional<ReportedAffectedConnectionPoints> buildAffectedCps(OperationExecution operationExecution) {
190         if (operationExecution.getOperationType() == OperationType.TERMINATE) {
191             String terminationType = childElement(new Gson().toJsonTree(operationExecution.getOperationParams()).getAsJsonObject(), "terminationType").getAsString();
192             if (TerminationType.FORCEFUL.name().equals(terminationType)) {
193                 //in case of force full termination the Ansible is not executed, so the connection points can not be
194                 //calculated from operation execution result
195                 logger.warn("Unable to send information related to affected connection points during forceful termination");
196                 return empty();
197             }
198         }
199         try {
200             JsonElement root = new Gson().toJsonTree(operationExecution.getAdditionalData());
201             if (root.getAsJsonObject().has("operationResult")) {
202                 JsonObject operationResult = root.getAsJsonObject().get("operationResult").getAsJsonObject();
203                 if (isAbsent(operationResult, "cbam_pre") ||
204                         isAbsent(operationResult, "cbam_post")) {
205                     return handleFailure(operationExecution, null);
206                 } else {
207                     return of(new Gson().fromJson(operationResult, ReportedAffectedConnectionPoints.class));
208                 }
209             } else {
210                 return handleFailure(operationExecution, null);
211             }
212         } catch (Exception e) {
213             return handleFailure(operationExecution, e);
214         }
215     }
216
217     private boolean isAbsent(JsonObject operationResult, String key) {
218         return !operationResult.has(key) || !operationResult.get(key).isJsonArray();
219     }
220
221     private Optional<ReportedAffectedConnectionPoints> handleFailure(OperationExecution operationExecution, Exception e) {
222         if (operationExecution.getStatus() == OperationStatus.FAILED) {
223             logger.warn("The operation failed and the affected connection points were not reported");
224             return empty();
225         } else {
226             if (e != null) {
227                 throw buildFatalFailure(logger, PROBLEM, e);
228             }
229             throw buildFatalFailure(logger, PROBLEM);
230         }
231     }
232 }