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