package org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.notification;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
+import com.google.common.collect.Ordering;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import com.nokia.cbam.lcm.v32.ApiException;
import com.nokia.cbam.lcm.v32.api.OperationExecutionsApi;
import com.nokia.cbam.lcm.v32.api.VnfsApi;
import com.nokia.cbam.lcm.v32.model.*;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.api.INotificationSender;
import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.SystemFunctions;
import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider;
import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.DriverProperties;
import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.ILifecycleChangeNotificationManager;
+import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.LifecycleManager;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Set;
+import static java.util.Optional.empty;
+import static java.util.Optional.of;
-import static com.google.common.collect.Iterables.filter;
+import static com.google.common.collect.Iterables.find;
import static com.google.common.collect.Iterables.tryFind;
import static com.google.common.collect.Sets.newConcurrentHashSet;
+import static com.google.common.collect.Sets.newHashSet;
import static com.nokia.cbam.lcm.v32.model.OperationType.INSTANTIATE;
+import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.buildFatalFailure;
import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.childElement;
-import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.fatalFailure;
import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCM_API_VERSION;
import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCN_API_VERSION;
import static org.slf4j.LoggerFactory.getLogger;
public class LifecycleChangeNotificationManager implements ILifecycleChangeNotificationManager {
public static final String PROBLEM = "All operations must return the { \"operationResult\" : { \"cbam_pre\" : [<fillMeOut>], \"cbam_post\" : [<fillMeOut>] } } structure";
+ /**
+ * Order the operations by start time (latest first)
+ */
+ public static final Ordering<OperationExecution> NEWEST_OPERATIONS_FIRST = new Ordering<OperationExecution>() {
+ @Override
+ public int compare(OperationExecution left, OperationExecution right) {
+ return right.getStartTime().toLocalDate().compareTo(left.getStartTime().toLocalDate());
+ }
+ };
/**
* < Separates the VNF id and the resource id within a VNF
*/
- private static final Set<OperationStatus> terminalStatus = Sets.newHashSet(OperationStatus.FINISHED, OperationStatus.FAILED);
+ private static final Set<OperationStatus> terminalStatus = newHashSet(OperationStatus.FINISHED, OperationStatus.FAILED);
private static Logger logger = getLogger(LifecycleChangeNotificationManager.class);
private final CbamRestApiProvider restApiProvider;
this.restApiProvider = restApiProvider;
}
+ /**
+ * @param status the status of the operation
+ * @return has the operation finished
+ */
+ public static boolean isTerminal(OperationStatus status) {
+ return terminalStatus.contains(status);
+ }
+
@VisibleForTesting
static OperationExecution findLastInstantiationBefore(List<OperationExecution> operationExecutions, OperationExecution currentOperation) {
- for (OperationExecution opExs : filter(NEWEST_OPERATIONS_FIRST.sortedCopy(operationExecutions), (OperationExecution opex2) -> !opex2.getStartTime().isAfter(currentOperation.getStartTime()))) {
- if (INSTANTIATE.equals(opExs.getOperationType()) &&
- (opExs.getStartTime().toLocalDate().isBefore(currentOperation.getStartTime().toLocalDate()) ||
- opExs.getStartTime().toLocalDate().isEqual(currentOperation.getStartTime().toLocalDate())
- )) {
- return opExs;
- }
- }
- throw new NoSuchElementException();
+ return find(NEWEST_OPERATIONS_FIRST.sortedCopy(operationExecutions), (OperationExecution opex2) ->
+ !opex2.getStartTime().isAfter(currentOperation.getStartTime())
+ && INSTANTIATE.equals(opex2.getOperationType()));
}
@Override
- public void handleLcn(VnfLifecycleChangeNotification recievedNotification) {
- logger.info("Received LCN: " + new Gson().toJson(recievedNotification));
+ public void handleLcn(VnfLifecycleChangeNotification receivedNotification) {
+ if (logger.isInfoEnabled()) {
+ logger.info("Received LCN: {}", new Gson().toJson(receivedNotification));
+ }
VnfsApi cbamLcmApi = restApiProvider.getCbamLcmApi(driverProperties.getVnfmId());
try {
- List<VnfInfo> vnfs = cbamLcmApi.vnfsGet(NOKIA_LCM_API_VERSION);
- com.google.common.base.Optional<VnfInfo> currentVnf = tryFind(vnfs, vnf -> vnf.getId().equals(recievedNotification.getVnfInstanceId()));
+ List<VnfInfo> vnfs = cbamLcmApi.vnfsGet(NOKIA_LCM_API_VERSION).blockingFirst();
+ com.google.common.base.Optional<VnfInfo> currentVnf = tryFind(vnfs, vnf -> vnf.getId().equals(receivedNotification.getVnfInstanceId()));
+ String vnfHeader = "The VNF with " + receivedNotification.getVnfInstanceId() + " identifier";
if (!currentVnf.isPresent()) {
- logger.warn("The VNF with " + recievedNotification.getVnfInstanceId() + " disappeared before being able to process the LCN");
+ logger.warn(vnfHeader + " disappeared before being able to process the LCN");
//swallow LCN
return;
} else {
- VnfInfo vnf = cbamLcmApi.vnfsVnfInstanceIdGet(recievedNotification.getVnfInstanceId(), NOKIA_LCN_API_VERSION);
- com.google.common.base.Optional<VnfProperty> externalVnfmId = tryFind(vnf.getExtensions(), prop -> prop.getName().equals(EXTERNAL_VNFM_ID));
+ VnfInfo vnf = cbamLcmApi.vnfsVnfInstanceIdGet(receivedNotification.getVnfInstanceId(), NOKIA_LCN_API_VERSION).blockingFirst();
+ com.google.common.base.Optional<VnfProperty> externalVnfmId = tryFind(vnf.getExtensions(), prop -> prop.getName().equals(LifecycleManager.EXTERNAL_VNFM_ID));
if (!externalVnfmId.isPresent()) {
- logger.warn("The VNF with " + vnf.getId() + " identifier is not a managed VNF");
+ logger.warn(vnfHeader + " is not a managed VNF");
return;
}
if (!externalVnfmId.get().getValue().equals(driverProperties.getVnfmId())) {
- logger.warn("The VNF with " + vnf.getId() + " identifier is not a managed by the VNFM with id " + externalVnfmId.get().getValue());
+ logger.warn(vnfHeader + " is not a managed by the VNFM with id " + externalVnfmId.get().getValue());
return;
}
}
} catch (Exception e) {
- logger.error("Unable to list VNFs / query VNF", e);
- throw new RuntimeException("Unable to list VNFs / query VNF", e);
+ throw buildFatalFailure(logger, "Unable to list VNFs / query VNF", e);
}
OperationExecutionsApi cbamOperationExecutionApi = restApiProvider.getCbamOperationExecutionApi(driverProperties.getVnfmId());
+ List<OperationExecution> operationExecutions;
try {
- List<OperationExecution> operationExecutions = cbamLcmApi.vnfsVnfInstanceIdOperationExecutionsGet(recievedNotification.getVnfInstanceId(), NOKIA_LCM_API_VERSION);
- OperationExecution operationExecution = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdGet(recievedNotification.getLifecycleOperationOccurrenceId(), NOKIA_LCM_API_VERSION);
- OperationExecution closestInstantiationToOperation = findLastInstantiationBefore(operationExecutions, operationExecution);
- String vimId;
- try {
- Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(closestInstantiationToOperation.getId(), NOKIA_LCM_API_VERSION);
- vimId = getVimId(operationParams);
- } catch (Exception e) {
- logger.error("Unable to detect last instantiation operation", e);
- throw new RuntimeException("Unable to detect last instantiation operation", e);
- }
- notificationSender.processNotification(recievedNotification, operationExecution, buildAffectedCps(operationExecution), vimId);
- if (OperationType.TERMINATE.equals(recievedNotification.getOperation()) && terminalStatus.contains(recievedNotification.getStatus())) {
- processedNotifications.add(new ProcessedNotification(recievedNotification.getLifecycleOperationOccurrenceId(), recievedNotification.getStatus()));
- }
- } catch (ApiException e) {
- logger.error("Unable to retrieve the current VNF " + recievedNotification.getVnfInstanceId(), e);
- throw new RuntimeException("Unable to retrieve the current VNF " + recievedNotification.getVnfInstanceId(), e);
+ operationExecutions = cbamLcmApi.vnfsVnfInstanceIdOperationExecutionsGet(receivedNotification.getVnfInstanceId(), NOKIA_LCM_API_VERSION).blockingFirst();
+ } catch (Exception e) {
+ throw buildFatalFailure(logger, "Unable to retrieve the operation executions for the VNF " + receivedNotification.getVnfInstanceId(), e);
+ }
+ OperationExecution operationExecution;
+ try {
+ operationExecution = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdGet(receivedNotification.getLifecycleOperationOccurrenceId(), NOKIA_LCM_API_VERSION).blockingFirst();
+ } catch (Exception e) {
+ throw buildFatalFailure(logger, "Unable to retrieve the operation execution with " + receivedNotification.getLifecycleOperationOccurrenceId() + " identifier", e);
+ }
+ OperationExecution closestInstantiationToOperation = findLastInstantiationBefore(operationExecutions, operationExecution);
+ String vimId = getVimId(cbamOperationExecutionApi, closestInstantiationToOperation);
+ notificationSender.processNotification(receivedNotification, operationExecution, buildAffectedCps(operationExecution), vimId);
+ if (isTerminationFinished(receivedNotification)) {
+ //signal LifecycleManager to continue the deletion of the VNF
+ processedNotifications.add(new ProcessedNotification(receivedNotification.getLifecycleOperationOccurrenceId(), receivedNotification.getStatus()));
+ }
+ }
+
+ private boolean isTerminationFinished(VnfLifecycleChangeNotification receivedNotification) {
+ return OperationType.TERMINATE.equals(receivedNotification.getOperation()) && terminalStatus.contains(receivedNotification.getStatus());
+ }
+
+ private String getVimId(OperationExecutionsApi cbamOperationExecutionApi, OperationExecution closestInstantiationToOperation) {
+ try {
+ Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(closestInstantiationToOperation.getId(), NOKIA_LCM_API_VERSION).blockingFirst();
+ return getVimId(operationParams);
+ } catch (Exception e) {
+ throw buildFatalFailure(logger, "Unable to detect last instantiation operation", e);
}
}
@Override
public void waitForTerminationToBeProcessed(String operationExecutionId) {
while (true) {
- com.google.common.base.Optional<ProcessedNotification> notification = Iterables.tryFind(processedNotifications, processedNotification -> processedNotification.getOperationExecutionId().equals(operationExecutionId));
+ com.google.common.base.Optional<ProcessedNotification> notification = tryFind(processedNotifications, processedNotification -> processedNotification.getOperationExecutionId().equals(operationExecutionId));
if (notification.isPresent()) {
processedNotifications.remove(notification.get());
return;
return request.getVims().get(0).getId();
}
- private ReportedAffectedConnectionPoints buildAffectedCps(OperationExecution operationExecution) {
- switch (operationExecution.getOperationType()) {
- case TERMINATE:
- String terminationType = childElement(new Gson().toJsonTree(operationExecution.getOperationParams()).getAsJsonObject(), "terminationType").getAsString();
- if (TerminationType.FORCEFUL.name().equals(terminationType)) {
- //in case of force full termination the Ansible is not executed, so the connection points can not be
- //calculated from operation execution result
- logger.warn("Unable to send information related to affected connection points during forceful termination");
- return null;
- }
+ private Optional<ReportedAffectedConnectionPoints> buildAffectedCps(OperationExecution operationExecution) {
+ if (!isTerminal(operationExecution.getStatus())) {
+ //connection points can only be calculated after the operation has finished
+ return Optional.empty();
+ }
+ if (operationExecution.getOperationType() == OperationType.TERMINATE) {
+ String terminationType = childElement(new Gson().toJsonTree(operationExecution.getOperationParams()).getAsJsonObject(), "terminationType").getAsString();
+ if (TerminationType.FORCEFUL.name().equals(terminationType)) {
+ //in case of force full termination the Ansible is not executed, so the connection points can not be
+ //calculated from operation execution result
+ logger.warn("Unable to send information related to affected connection points during forceful termination");
+ return empty();
+ } else {
+ //graceful termination should be handled as any other operation
+ }
}
try {
JsonElement root = new Gson().toJsonTree(operationExecution.getAdditionalData());
if (root.getAsJsonObject().has("operationResult")) {
JsonObject operationResult = root.getAsJsonObject().get("operationResult").getAsJsonObject();
- if (!isPresent(operationResult, "cbam_pre") || !isPresent(operationResult, "cbam_post")) {
- handleFailure(operationExecution, null);
+ if (isAbsent(operationResult, "cbam_pre") ||
+ isAbsent(operationResult, "cbam_post")) {
+ return handleFailure(operationExecution, null);
+ } else {
+ return of(new Gson().fromJson(operationResult, ReportedAffectedConnectionPoints.class));
}
- return new Gson().fromJson(operationResult, ReportedAffectedConnectionPoints.class);
+ } else {
+ return handleFailure(operationExecution, null);
}
} catch (Exception e) {
- handleFailure(operationExecution, e);
+ return handleFailure(operationExecution, e);
}
- return new ReportedAffectedConnectionPoints();
}
- private boolean isPresent(JsonObject operationResult, String key) {
- return operationResult.has(key) && operationResult.get(key).isJsonArray();
+ private boolean isAbsent(JsonObject operationResult, String key) {
+ return !operationResult.has(key) || !operationResult.get(key).isJsonArray();
}
- private void handleFailure(OperationExecution operationExecution, Exception e) {
- switch (operationExecution.getStatus()) {
- case FAILED:
- case OTHER:
- logger.warn("The operation failed and the affected connection points were not reported");
- break;
- case STARTED: //can not happen (the changed resources are only executed for terminal state
- case FINISHED:
- if (e != null) {
- fatalFailure(logger, PROBLEM, e);
- }
- fatalFailure(logger, PROBLEM);
+ private Optional<ReportedAffectedConnectionPoints> handleFailure(OperationExecution operationExecution, Exception e) {
+ if (operationExecution.getStatus() == OperationStatus.FAILED) {
+ logger.warn("The operation failed and the affected connection points were not reported");
+ return empty();
+ } else {
+ if (e != null) {
+ throw buildFatalFailure(logger, PROBLEM, e);
+ }
+ throw buildFatalFailure(logger, PROBLEM);
}
}
}