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