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;
18 import com.google.common.collect.Ordering;
19 import com.google.common.collect.Sets;
20 import com.google.gson.Gson;
21 import com.google.gson.JsonElement;
22 import com.nokia.cbam.lcm.v32.api.OperationExecutionsApi;
23 import com.nokia.cbam.lcm.v32.api.VnfsApi;
24 import com.nokia.cbam.lcm.v32.model.OperationExecution;
25 import com.nokia.cbam.lcm.v32.model.OperationType;
26 import com.nokia.cbam.lcm.v32.model.VnfInfo;
28 import javax.servlet.http.HttpServletResponse;
29 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.onap.core.SelfRegistrationManager;
30 import org.onap.vnfmdriver.model.JobDetailInfo;
31 import org.onap.vnfmdriver.model.JobDetailInfoResponseDescriptor;
32 import org.onap.vnfmdriver.model.JobResponseInfo;
33 import org.onap.vnfmdriver.model.JobStatus;
34 import org.slf4j.Logger;
35 import org.springframework.beans.factory.annotation.Autowired;
36 import org.springframework.stereotype.Component;
38 import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
39 import static java.util.Optional.empty;
40 import static java.util.Optional.of;
42 import static com.google.common.base.Splitter.on;
43 import static com.google.common.collect.Iterables.find;
44 import static com.google.common.collect.Iterables.tryFind;
45 import static com.google.common.collect.Lists.newArrayList;
46 import static com.nokia.cbam.lcm.v32.model.OperationStatus.FAILED;
47 import static com.nokia.cbam.lcm.v32.model.OperationStatus.STARTED;
48 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.SEPARATOR;
49 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.buildFatalFailure;
50 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.SystemFunctions.systemFunctions;
51 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.CbamRestApiProvider.NOKIA_LCM_API_VERSION;
52 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.notification.LifecycleChangeNotificationManager.NEWEST_OPERATIONS_FIRST;
53 import static org.slf4j.LoggerFactory.getLogger;
54 import static org.springframework.util.StringUtils.isEmpty;
57 * Responsible for providing the status of jobs
58 * The job id is a composite field of the VNF identifier and an UUID.
59 * The second UUID is passed as mandatory parameter to each executed operation.
60 * This UUID is used to locate the operation execution from the ONAP job identifier
63 public class JobManager {
64 public static final String OPERATION_STARTED_DESCRIPTION = "Operation started";
65 private static final Ordering<JobResponseInfo> OLDEST_FIRST = new Ordering<JobResponseInfo>() {
67 public int compare(JobResponseInfo left, JobResponseInfo right) {
68 return Long.valueOf(left.getResponseId()).compareTo(Long.valueOf(right.getResponseId()));
71 private static Logger logger = getLogger(JobManager.class);
72 private final Set<String> ongoingJobs = Sets.newConcurrentHashSet();
73 private final CbamRestApiProvider cbamRestApiProvider;
74 private final SelfRegistrationManager selfRegistrationManager;
75 private volatile boolean preparingForShutDown = false;
78 JobManager(CbamRestApiProvider cbamRestApiProvider, SelfRegistrationManager selfRegistrationManager) {
79 this.cbamRestApiProvider = cbamRestApiProvider;
80 this.selfRegistrationManager = selfRegistrationManager;
84 * @param operationParams the operation execution
85 * @return the ONAP job identifier of belonging to the operation execution
87 public static String extractOnapJobId(Object operationParams) {
88 JsonElement operationParamsAsJson = new Gson().toJsonTree(operationParams);
89 JsonElement additionalParams = operationParamsAsJson.getAsJsonObject().get("additionalParams");
90 if (additionalParams == null) {
91 throw new NoSuchElementException("The operation result " + operationParamsAsJson + " does not contain the mandatory additionalParams structure");
93 JsonElement jobId = additionalParams.getAsJsonObject().get("jobId");
95 throw new NoSuchElementException("The operation result " + operationParamsAsJson + " does not contain the mandatory jobId in the additionalParams structure");
97 return jobId.getAsString();
101 * @return is the component preparing for shutdown
103 public boolean isPreparingForShutDown(){
104 return preparingForShutDown;
108 * Throws an exception in case the service is not ready to serve requests due to
109 * not being able to register to MSB or to subscribe to CBAM LCNs
111 * @param vnfId the identifier of the VNF
112 * @param response the HTTP response of the current sVNFM incomming request
113 * @return the identifier of the job
115 public String spawnJob(String vnfId, HttpServletResponse response) {
116 String jobId = vnfId + SEPARATOR + UUID.randomUUID().toString();
117 synchronized (this) {
118 if (preparingForShutDown) {
119 response.setStatus(SC_SERVICE_UNAVAILABLE);
120 throw buildFatalFailure(logger, "The service is preparing to shut down");
122 if (!selfRegistrationManager.isReady()) {
123 response.setStatus(SC_SERVICE_UNAVAILABLE);
124 throw buildFatalFailure(logger, "The service is not yet ready");
127 ongoingJobs.add(jobId);
132 * Signal that a job has finished
134 * @param jobId the identifier of the job
136 public void jobFinished(String jobId) {
137 ongoingJobs.remove(jobId);
141 * @return the system has any ongoing jobs
143 public boolean hasOngoingJobs() {
144 return !ongoingJobs.isEmpty();
149 * Wait for all jobs to be cleared from the system the refuses to let additional request in
151 public void prepareForShutdown() {
152 preparingForShutDown = true;
154 synchronized (this) {
155 if (!hasOngoingJobs()) {
159 systemFunctions().sleep(500L);
164 * @param vnfmId the identifier of the VNFM
165 * @param jobId the identifier of the job
166 * @return detailed information of the job
168 public JobDetailInfo getJob(String vnfmId, String jobId) {
169 logger.debug("Retrieving the details for job with {} identifier", jobId);
170 ArrayList<String> jobParts = newArrayList(on(SEPARATOR).split(jobId));
171 if (jobParts.size() != 2) {
172 throw new IllegalArgumentException("The jobId should be in the <vnfId>" + SEPARATOR + "<UUID> format, but was " + jobId);
174 String vnfId = jobParts.get(0);
175 if (isEmpty(vnfId)) {
176 throw new IllegalArgumentException("The vnfId in the jobId (" + jobId + ") can not be empty");
178 String operationExecutionId = jobParts.get(1);
179 if (isEmpty(operationExecutionId)) {
180 throw new IllegalArgumentException("The UUID in the jobId (" + jobId + ") can not be empty");
182 Optional<VnfInfo> vnf = getVnf(vnfmId, vnfId);
183 if (!vnf.isPresent()) {
184 return getJobDetailInfoForMissingVnf(jobId);
186 return getJobInfoForExistingVnf(vnfmId, jobId, vnfId, vnf.get());
190 private JobDetailInfo getJobDetailInfoForMissingVnf(String jobId) {
191 if (ongoingJobs.contains(jobId)) {
192 return reportOngoing(jobId);
194 return reportFinished(jobId);
198 private JobDetailInfo getJobInfoForExistingVnf(String vnfmId, String jobId, String vnfId, VnfInfo vnf) {
200 OperationExecution operation = findOperationByJobId(vnfmId, vnf, jobId);
201 return getJobDetailInfo(vnfmId, jobId, vnfId, operation);
202 } catch (NoSuchElementException e) {
203 logger.warn("No operation could be identified for job with {} identifier", jobId, e);
204 if (ongoingJobs.contains(jobId)) {
205 return reportOngoing(jobId);
207 return reportFailed(jobId, "The requested operation was not able to start on CBAM");
212 private JobDetailInfo getJobDetailInfo(String vnfmId, String jobId, String vnfId, OperationExecution operation) {
213 if (operation.getStatus() == STARTED) {
214 return reportOngoing(jobId);
215 } else if (operation.getStatus() == FAILED) {
216 return reportFailed(jobId, operation.getError().getTitle() + ": " + operation.getError().getDetail());
218 return getJobForTerminalOperationState(vnfmId, jobId, vnfId, operation);
222 private JobDetailInfo getJobForTerminalOperationState(String vnfmId, String jobId, String vnfId, OperationExecution operation) {
223 //termination includes VNF deletion in ONAP terminology
224 if (operation.getOperationType() == com.nokia.cbam.lcm.v32.model.OperationType.TERMINATE) {
225 if (ongoingJobs.contains(jobId)) {
226 return reportOngoing(jobId);
228 //the VNF must be queried again since it could have been deleted since the VNF has been terminated
229 if (getVnf(vnfmId, vnfId).isPresent()) {
230 return reportFailed(jobId, "unable to delete VNF");
232 return reportFinished(jobId);
236 return reportFinished(jobId);
240 private JobDetailInfo buildJob(String jobId, JobResponseInfo... history) {
241 JobDetailInfo job = new JobDetailInfo();
243 JobDetailInfoResponseDescriptor jobDetailInfoResponseDescriptor = new JobDetailInfoResponseDescriptor();
244 job.setResponseDescriptor(jobDetailInfoResponseDescriptor);
245 List<JobResponseInfo> oldestFirst = OLDEST_FIRST.sortedCopy(newArrayList(history));
246 JobResponseInfo newestJob = oldestFirst.get(oldestFirst.size() - 1);
247 jobDetailInfoResponseDescriptor.setResponseId(newestJob.getResponseId());
248 jobDetailInfoResponseDescriptor.setStatus(JobStatus.valueOf(newestJob.getStatus()));
249 jobDetailInfoResponseDescriptor.setProgress(newestJob.getProgress());
250 jobDetailInfoResponseDescriptor.setStatusDescription(newestJob.getStatusDescription());
251 jobDetailInfoResponseDescriptor.setErrorCode(newestJob.getErrorCode());
252 jobDetailInfoResponseDescriptor.setResponseHistoryList(oldestFirst);
256 private JobResponseInfo buildJobPart(String description, JobStatus status, Integer progress, Integer responseId) {
257 JobResponseInfo currentJob = new JobResponseInfo();
258 currentJob.setProgress(progress.toString());
259 currentJob.setResponseId(responseId.toString());
260 currentJob.setStatus(status.name());
261 currentJob.setStatusDescription(description);
265 private JobDetailInfo reportOngoing(String jobId) {
266 return buildJob(jobId, buildJobPart(OPERATION_STARTED_DESCRIPTION, JobStatus.STARTED, 50, 1));
269 private JobDetailInfo reportFailed(String jobId, String reason) {
270 return buildJob(jobId,
271 buildJobPart(OPERATION_STARTED_DESCRIPTION, JobStatus.STARTED, 50, 1),
272 buildJobPart("Operation failed due to " + reason, JobStatus.ERROR, 100, 2)
276 private JobDetailInfo reportFinished(String jobId) {
277 return buildJob(jobId,
278 buildJobPart(OPERATION_STARTED_DESCRIPTION, JobStatus.STARTED, 50, 1),
279 buildJobPart("Operation finished", JobStatus.FINISHED, 100, 2)
283 private OperationExecution findOperationByJobId(String vnfmId, VnfInfo vnf, String jobId) {
284 OperationExecutionsApi cbamOperationExecutionApi = cbamRestApiProvider.getCbamOperationExecutionApi(vnfmId);
285 //the operations are sorted so that the newest operations are queried first
286 //performance optimization that usually the core system is interested in the operations executed last
287 if (vnf.getOperationExecutions() != null) {
288 List<OperationExecution> sortedOperation = NEWEST_OPERATIONS_FIRST.sortedCopy(vnf.getOperationExecutions());
289 return find(sortedOperation, operation -> isCurrentOperationTriggeredByJob(jobId, cbamOperationExecutionApi, operation));
291 throw new NoSuchElementException();
294 private boolean isCurrentOperationTriggeredByJob(String jobId, OperationExecutionsApi cbamOperationExecutionApi, OperationExecution operationExecution) {
295 if (OperationType.MODIFY_INFO.equals(operationExecution.getOperationType())) {
296 //the modify info is never triggered by an external job
300 Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(operationExecution.getId(), NOKIA_LCM_API_VERSION).blockingFirst();
301 if (extractOnapJobId(operationParams).equals(jobId)) {
304 } catch (Exception e) {
305 throw buildFatalFailure(logger, "Unable to retrieve operation parameters of operation with " + operationExecution.getId() + " identifier", e);
310 private Optional<VnfInfo> getVnf(String vnfmId, String vnfId) {
312 //test if the VNF exists (required to be able to distingush between failed request )
313 VnfsApi cbamLcmApi = cbamRestApiProvider.getCbamLcmApi(vnfmId);
314 logger.debug("Listing VNFs");
315 List<VnfInfo> vnfs = cbamLcmApi.vnfsGet(NOKIA_LCM_API_VERSION).blockingSingle();
316 com.google.common.base.Optional<VnfInfo> vnf = tryFind(vnfs, vnfInfo -> vnfId.equals(vnfInfo.getId()));
317 if (!vnf.isPresent()) {
318 logger.debug("VNF with {} identifier is missing", vnfId);
321 logger.debug("VNF with {} identifier still exists", vnfId);
322 //query the VNF again to get operation execution result
323 return of(cbamLcmApi.vnfsVnfInstanceIdGet(vnfId, NOKIA_LCM_API_VERSION).blockingFirst());
325 } catch (Exception e) {
326 throw buildFatalFailure(logger, "Unable to retrieve VNF with " + vnfId + " identifier", e);