2  * Copyright 2016-2017, Nokia Corporation
 
   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
 
   8  *     http://www.apache.org/licenses/LICENSE-2.0
 
  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.
 
  16 package org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.notification;
 
  18 import com.google.common.annotations.VisibleForTesting;
 
  19 import com.google.common.collect.Iterables;
 
  20 import com.google.common.collect.Sets;
 
  21 import com.google.gson.Gson;
 
  22 import com.google.gson.JsonElement;
 
  23 import com.google.gson.JsonObject;
 
  24 import com.nokia.cbam.lcm.v32.ApiException;
 
  25 import com.nokia.cbam.lcm.v32.api.OperationExecutionsApi;
 
  26 import com.nokia.cbam.lcm.v32.api.VnfsApi;
 
  27 import com.nokia.cbam.lcm.v32.model.*;
 
  28 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.api.INotificationSender;
 
  29 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.SystemFunctions;
 
  30 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider;
 
  31 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.DriverProperties;
 
  32 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.ILifecycleChangeNotificationManager;
 
  33 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.LifecycleManager;
 
  34 import org.slf4j.Logger;
 
  35 import org.springframework.beans.factory.annotation.Autowired;
 
  36 import org.springframework.stereotype.Component;
 
  38 import java.util.List;
 
  39 import java.util.NoSuchElementException;
 
  42 import static com.google.common.collect.Iterables.filter;
 
  43 import static com.google.common.collect.Iterables.tryFind;
 
  44 import static com.google.common.collect.Sets.newConcurrentHashSet;
 
  45 import static com.nokia.cbam.lcm.v32.model.OperationType.INSTANTIATE;
 
  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.util.CbamUtils.fatalFailure;
 
  48 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCM_API_VERSION;
 
  49 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCN_API_VERSION;
 
  50 import static org.slf4j.LoggerFactory.getLogger;
 
  53  * Responsible for handling lifecycle change notifications from CBAM.
 
  54  * The received LCNs are transformed into ONAP LCNs.
 
  55  * The following CBAM LCNs are processed:
 
  60  * The current limitations
 
  61  * - if a LCN can not be be processed due to VNF having been deleted the problem is logged and CBAM is notified that
 
  62  * the LCN has been processed (even if not in reality) because the signaling of failed LCN delivery blocks the delivery
 
  63  * on all LCN deliveries. The consequence of this is that the information known by VF-C / A&AI may be inconsistent with
 
  64  * reality (VNF having been deleted)
 
  67 public class LifecycleChangeNotificationManager implements ILifecycleChangeNotificationManager {
 
  69     public static final String PROBLEM = "All operations must return the { \"operationResult\" : { \"cbam_pre\" : [<fillMeOut>], \"cbam_post\" : [<fillMeOut>] } } structure";
 
  71      * < Separates the VNF id and the resource id within a VNF
 
  73     private static final Set<OperationStatus> terminalStatus = Sets.newHashSet(OperationStatus.FINISHED, OperationStatus.FAILED);
 
  74     private static Logger logger = getLogger(LifecycleChangeNotificationManager.class);
 
  76     private final CbamRestApiProvider restApiProvider;
 
  77     private final DriverProperties driverProperties;
 
  78     private final INotificationSender notificationSender;
 
  79     private Set<ProcessedNotification> processedNotifications = newConcurrentHashSet();
 
  82     LifecycleChangeNotificationManager(CbamRestApiProvider restApiProvider, DriverProperties driverProperties, INotificationSender notificationSender) {
 
  83         this.notificationSender = notificationSender;
 
  84         this.driverProperties = driverProperties;
 
  85         this.restApiProvider = restApiProvider;
 
  89     static OperationExecution findLastInstantiationBefore(List<OperationExecution> operationExecutions, OperationExecution currentOperation) {
 
  90         for (OperationExecution opExs : filter(NEWEST_OPERATIONS_FIRST.sortedCopy(operationExecutions), (OperationExecution opex2) -> !opex2.getStartTime().isAfter(currentOperation.getStartTime()))) {
 
  91             if (INSTANTIATE.equals(opExs.getOperationType()) &&
 
  92                     (opExs.getStartTime().toLocalDate().isBefore(currentOperation.getStartTime().toLocalDate()) ||
 
  93                             opExs.getStartTime().toLocalDate().isEqual(currentOperation.getStartTime().toLocalDate())
 
  98         throw new NoSuchElementException();
 
 102     public void handleLcn(VnfLifecycleChangeNotification recievedNotification) {
 
 103         if (logger.isInfoEnabled()) {
 
 104             logger.info("Received LCN: " + new Gson().toJson(recievedNotification));
 
 106         VnfsApi cbamLcmApi = restApiProvider.getCbamLcmApi(driverProperties.getVnfmId());
 
 108             List<VnfInfo> vnfs = cbamLcmApi.vnfsGet(NOKIA_LCM_API_VERSION);
 
 109             com.google.common.base.Optional<VnfInfo> currentVnf = tryFind(vnfs, vnf -> vnf.getId().equals(recievedNotification.getVnfInstanceId()));
 
 110             if (!currentVnf.isPresent()) {
 
 111                 logger.warn("The VNF with " + recievedNotification.getVnfInstanceId() + " disappeared before being able to process the LCN");
 
 115                 VnfInfo vnf = cbamLcmApi.vnfsVnfInstanceIdGet(recievedNotification.getVnfInstanceId(), NOKIA_LCN_API_VERSION);
 
 116                 com.google.common.base.Optional<VnfProperty> externalVnfmId = tryFind(vnf.getExtensions(), prop -> prop.getName().equals(LifecycleManager.EXTERNAL_VNFM_ID));
 
 117                 if (!externalVnfmId.isPresent()) {
 
 118                     logger.warn("The VNF with " + vnf.getId() + " identifier is not a managed VNF");
 
 121                 if (!externalVnfmId.get().getValue().equals(driverProperties.getVnfmId())) {
 
 122                     logger.warn("The VNF with " + vnf.getId() + " identifier is not a managed by the VNFM with id " + externalVnfmId.get().getValue());
 
 126         } catch (Exception e) {
 
 127             fatalFailure(logger, "Unable to list VNFs / query VNF", e);
 
 129         OperationExecutionsApi cbamOperationExecutionApi = restApiProvider.getCbamOperationExecutionApi(driverProperties.getVnfmId());
 
 131             List<OperationExecution> operationExecutions = cbamLcmApi.vnfsVnfInstanceIdOperationExecutionsGet(recievedNotification.getVnfInstanceId(), NOKIA_LCM_API_VERSION);
 
 132             OperationExecution operationExecution = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdGet(recievedNotification.getLifecycleOperationOccurrenceId(), NOKIA_LCM_API_VERSION);
 
 133             OperationExecution closestInstantiationToOperation = findLastInstantiationBefore(operationExecutions, operationExecution);
 
 134             String vimId = getVimId(cbamOperationExecutionApi, closestInstantiationToOperation);
 
 135             notificationSender.processNotification(recievedNotification, operationExecution, buildAffectedCps(operationExecution), vimId);
 
 136             if (OperationType.TERMINATE.equals(recievedNotification.getOperation()) && terminalStatus.contains(recievedNotification.getStatus())) {
 
 137                 processedNotifications.add(new ProcessedNotification(recievedNotification.getLifecycleOperationOccurrenceId(), recievedNotification.getStatus()));
 
 139         } catch (ApiException e) {
 
 140             fatalFailure(logger, "Unable to retrieve the current VNF " + recievedNotification.getVnfInstanceId(), e);
 
 144     private String getVimId(OperationExecutionsApi cbamOperationExecutionApi, OperationExecution closestInstantiationToOperation) {
 
 146             Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(closestInstantiationToOperation.getId(), NOKIA_LCM_API_VERSION);
 
 147             return getVimId(operationParams);
 
 148         } catch (Exception e) {
 
 149             throw fatalFailure(logger, "Unable to detect last instantiation operation", e);
 
 154     public void waitForTerminationToBeProcessed(String operationExecutionId) {
 
 156             com.google.common.base.Optional<ProcessedNotification> notification = Iterables.tryFind(processedNotifications, processedNotification -> processedNotification.getOperationExecutionId().equals(operationExecutionId));
 
 157             if (notification.isPresent()) {
 
 158                 processedNotifications.remove(notification.get());
 
 161             SystemFunctions.systemFunctions().sleep(500);
 
 165     private String getVimId(Object instantiationParameters) {
 
 166         InstantiateVnfRequest request = new Gson().fromJson(new Gson().toJson(instantiationParameters), InstantiateVnfRequest.class);
 
 167         return request.getVims().get(0).getId();
 
 170     private ReportedAffectedConnectionPoints buildAffectedCps(OperationExecution operationExecution) {
 
 171         if (operationExecution.getOperationType() == OperationType.TERMINATE) {
 
 172             String terminationType = childElement(new Gson().toJsonTree(operationExecution.getOperationParams()).getAsJsonObject(), "terminationType").getAsString();
 
 173             if (TerminationType.FORCEFUL.name().equals(terminationType)) {
 
 174                 //in case of force full termination the Ansible is not executed, so the connection points can not be
 
 175                 //calculated from operation execution result
 
 176                 logger.warn("Unable to send information related to affected connection points during forceful termination");
 
 181             JsonElement root = new Gson().toJsonTree(operationExecution.getAdditionalData());
 
 182             if (root.getAsJsonObject().has("operationResult")) {
 
 183                 JsonObject operationResult = root.getAsJsonObject().get("operationResult").getAsJsonObject();
 
 184                 if (!isPresent(operationResult, "cbam_pre") || !isPresent(operationResult, "cbam_post")) {
 
 185                     handleFailure(operationExecution, null);
 
 187                 return new Gson().fromJson(operationResult, ReportedAffectedConnectionPoints.class);
 
 189         } catch (Exception e) {
 
 190             handleFailure(operationExecution, e);
 
 192         return new ReportedAffectedConnectionPoints();
 
 195     private boolean isPresent(JsonObject operationResult, String key) {
 
 196         return operationResult.has(key) && operationResult.get(key).isJsonArray();
 
 199     private void handleFailure(OperationExecution operationExecution, Exception e) {
 
 200         switch (operationExecution.getStatus()) {
 
 203                 logger.warn("The operation failed and the affected connection points were not reported");
 
 205             case STARTED: //can not happen (the changed resources are only executed for terminal state
 
 208                     fatalFailure(logger, PROBLEM, e);
 
 210                 fatalFailure(logger, PROBLEM);