Merge "Updating Nokia driver"
[vfc/nfvo/driver/vnfm/svnfm.git] / nokiav2 / driver / src / main / java / org / onap / vfc / nfvo / driver / vnfm / svnfm / nokia / vnfm / JobManager.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;
17
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.ApiException;
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.OperationExecution;
26 import com.nokia.cbam.lcm.v32.model.VnfInfo;
27 import org.apache.http.HttpStatus;
28 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.onap.core.SelfRegistrationManager;
29 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm.notification.LifecycleChangeNotificationManager;
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;
37
38 import javax.servlet.http.HttpServletResponse;
39 import java.util.*;
40
41 import static com.google.common.base.Splitter.on;
42 import static com.google.common.collect.Iterables.tryFind;
43 import static com.google.common.collect.Lists.newArrayList;
44 import static java.util.Optional.empty;
45 import static java.util.Optional.of;
46 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.CbamUtils.fatalFailure;
47 import static org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.util.SystemFunctions.systemFunctions;
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.ILifecycleChangeNotificationManager.SEPARATOR;
50 import static org.slf4j.LoggerFactory.getLogger;
51 import static org.springframework.util.StringUtils.isEmpty;
52
53 /**
54  * Responsible for providing the status of jobs
55  * The job id is a composite field of the VNF identifier and an UUID.
56  * The second UUID is passed as mandatory parameter to each executed operation.
57  * This UUID is used to locate the operation execution from the ONAP job identifier
58  */
59 @Component
60 public class JobManager {
61     private static final Ordering<JobResponseInfo> OLDEST_FIRST = new Ordering<JobResponseInfo>() {
62         @Override
63         public int compare(JobResponseInfo left, JobResponseInfo right) {
64             return Long.valueOf(left.getResponseId()).compareTo(Long.valueOf(right.getResponseId()));
65         }
66     };
67     private static Logger logger = getLogger(JobManager.class);
68     private final Set<String> ongoingJobs = Sets.newConcurrentHashSet();
69     private final CbamRestApiProvider cbamRestApiProvider;
70     private final SelfRegistrationManager selfRegistrationManager;
71     private volatile boolean preparingForShutDown = false;
72
73     @Autowired
74     JobManager(CbamRestApiProvider cbamRestApiProvider, SelfRegistrationManager selfRegistrationManager) {
75         this.cbamRestApiProvider = cbamRestApiProvider;
76         this.selfRegistrationManager = selfRegistrationManager;
77     }
78
79     /**
80      * @param operationParams the operation execution
81      * @return the ONAP job identifier of belonging to the operation execution
82      */
83     public static String extractOnapJobId(Object operationParams) {
84         JsonElement operationParamsAsJson = new Gson().toJsonTree(operationParams);
85         JsonElement additionalParams = operationParamsAsJson.getAsJsonObject().get("additionalParams");
86         if (additionalParams == null) {
87             throw new NoSuchElementException("The operation result " + operationParamsAsJson + " does not contain the mandatory additionalParams structure");
88         }
89         JsonElement jobId = additionalParams.getAsJsonObject().get("jobId");
90         if (jobId == null) {
91             throw new NoSuchElementException("The operation result " + operationParamsAsJson + " does not contain the mandatory jobId in the additionalParams structure");
92         }
93         return jobId.getAsString();
94     }
95
96     /**
97      * Throws an exception in case the service is not ready to serve requests due to
98      * not being able to register to MSB or to subscribe to CBAM LCNs
99      *
100      * @param vnfId    the identifier of the VNF
101      * @param response the HTTP response of the current sVNFM incomming request
102      * @return the identifier of the job
103      */
104     public String spawnJob(String vnfId, HttpServletResponse response) {
105         String jobId = vnfId + SEPARATOR + UUID.randomUUID().toString();
106         synchronized (this) {
107             if (preparingForShutDown) {
108                 response.setStatus(HttpStatus.SC_SERVICE_UNAVAILABLE);
109                 fatalFailure(logger, "The service is preparing to shut down");
110             }
111             if (!selfRegistrationManager.isReady()) {
112                 response.setStatus(HttpStatus.SC_SERVICE_UNAVAILABLE);
113                 fatalFailure(logger, "The service is not yet ready");
114             }
115         }
116         ongoingJobs.add(jobId);
117         return jobId;
118     }
119
120     /**
121      * Signal that a job has finished
122      *
123      * @param jobId the identifier of the job
124      */
125     public void jobFinished(String jobId) {
126         ongoingJobs.remove(jobId);
127     }
128
129     /**
130      * @return the system has any ongoing jobs
131      */
132     public boolean hasOngoingJobs() {
133         return ongoingJobs.size() != 0;
134     }
135
136
137     /**
138      * Wait for all jobs to be cleared from the system the refuses to let additional request in
139      */
140     public void prepareForShutdown() {
141         preparingForShutDown = true;
142         while (true) {
143             synchronized (this) {
144                 if (!hasOngoingJobs()) {
145                     return;
146                 }
147             }
148             systemFunctions().sleep(500L);
149         }
150     }
151
152     /**
153      * @param vnfmId the identifier of the VNFM
154      * @param jobId  the identifier of the job
155      * @return detailed information of the job
156      */
157     public JobDetailInfo getJob(String vnfmId, String jobId) {
158         logger.debug("Retrieving the details for job with " + jobId);
159         ArrayList<String> jobParts = newArrayList(on(SEPARATOR).split(jobId));
160         if (jobParts.size() != 2) {
161             throw new IllegalArgumentException("The jobId should be in the <vnfId>" + SEPARATOR + "<UUID> format, but was " + jobId);
162         }
163         String vnfId = jobParts.get(0);
164         if (isEmpty(vnfId)) {
165             throw new IllegalArgumentException("The vnfId in the jobId (" + jobId + ") can not be empty");
166         }
167         String operationExecutionId = jobParts.get(1);
168         if (isEmpty(operationExecutionId)) {
169             throw new IllegalArgumentException("The UUID in the jobId (" + jobId + ") can not be empty");
170         }
171         Optional<VnfInfo> vnf = getVnf(vnfmId, vnfId);
172         if (!vnf.isPresent()) {
173             return getJobDetailInfoForMissingVnf(jobId);
174         } else {
175             return getJobInfoForExistingVnf(vnfmId, jobId, vnfId, vnf);
176         }
177     }
178
179     private JobDetailInfo getJobDetailInfoForMissingVnf(String jobId) {
180         if (ongoingJobs.contains(jobId)) {
181             return reportOngoing(jobId);
182         } else {
183             return reportFinished(jobId);
184         }
185     }
186
187     private JobDetailInfo getJobInfoForExistingVnf(String vnfmId, String jobId, String vnfId, Optional<VnfInfo> vnf) {
188         try {
189             OperationExecution operation = findOperationByJobId(vnfmId, vnf, jobId);
190             switch (operation.getStatus()) {
191                 case STARTED:
192                     return reportOngoing(jobId);
193                 case FINISHED:
194                 case OTHER:
195                     switch (operation.getOperationType()) {
196                         case TERMINATE:
197                             //termination includes VNF deletion in ONAP terminology
198                             if (ongoingJobs.contains(jobId)) {
199                                 return reportOngoing(jobId);
200                             } else {
201                                 //the VNF must be queried again since it could have been deleted since the VNF has been terminated
202                                 if (getVnf(vnfmId, vnfId).isPresent()) {
203                                     return reportFailed(jobId, "unable to delete VNF");
204                                 } else {
205                                     return reportFinished(jobId);
206                                 }
207                             }
208                         default:
209                             return reportFinished(jobId);
210                     }
211                 default: //all cases handled
212                 case FAILED:
213                     return reportFailed(jobId, operation.getError().getTitle() + ": " + operation.getError().getDetail());
214             }
215         } catch (NoSuchElementException e) {
216             if (ongoingJobs.contains(jobId)) {
217                 return reportOngoing(jobId);
218             } else {
219                 return reportFailed(jobId, "The requested operation was not able to start on CBAM");
220             }
221         }
222     }
223
224     private JobDetailInfo buildJob(String jobId, JobResponseInfo... history) {
225         JobDetailInfo job = new JobDetailInfo();
226         job.setJobId(jobId);
227         JobDetailInfoResponseDescriptor jobDetailInfoResponseDescriptor = new JobDetailInfoResponseDescriptor();
228         job.setResponseDescriptor(jobDetailInfoResponseDescriptor);
229         List<JobResponseInfo> oldestFirst = OLDEST_FIRST.sortedCopy(newArrayList(history));
230         JobResponseInfo newestJob = oldestFirst.get(oldestFirst.size() - 1);
231         jobDetailInfoResponseDescriptor.setResponseId(newestJob.getResponseId());
232         jobDetailInfoResponseDescriptor.setStatus(JobStatus.valueOf(newestJob.getStatus()));
233         jobDetailInfoResponseDescriptor.setProgress(newestJob.getProgress());
234         jobDetailInfoResponseDescriptor.setStatusDescription(newestJob.getStatusDescription());
235         jobDetailInfoResponseDescriptor.setErrorCode(newestJob.getErrorCode());
236         jobDetailInfoResponseDescriptor.setResponseHistoryList(oldestFirst);
237         return job;
238     }
239
240     private JobResponseInfo buildJobPart(String description, JobStatus status, Integer progress, Integer responseId) {
241         JobResponseInfo currentJob = new JobResponseInfo();
242         currentJob.setProgress(progress.toString());
243         currentJob.setResponseId(responseId.toString());
244         currentJob.setStatus(status.name());
245         currentJob.setStatusDescription(description);
246         return currentJob;
247     }
248
249     private JobDetailInfo reportOngoing(String jobId) {
250         return buildJob(jobId, buildJobPart("Operation started", JobStatus.STARTED, 50, 1));
251     }
252
253     private JobDetailInfo reportFailed(String jobId, String reason) {
254         return buildJob(jobId,
255                 buildJobPart("Operation started", JobStatus.STARTED, 50, 1),
256                 buildJobPart("Operation failed due to " + reason, JobStatus.ERROR, 100, 2)
257         );
258     }
259
260     private JobDetailInfo reportFinished(String jobId) {
261         return buildJob(jobId,
262                 buildJobPart("Operation started", JobStatus.STARTED, 50, 1),
263                 buildJobPart("Operation finished", JobStatus.FINISHED, 100, 2)
264         );
265     }
266
267     private OperationExecution findOperationByJobId(String vnfmId, Optional<VnfInfo> vnf, String jobId) {
268         OperationExecutionsApi cbamOperationExecutionApi = cbamRestApiProvider.getCbamOperationExecutionApi(vnfmId);
269         //the operations are sorted so that the newest operations are queried first
270         //performance optimization that usually the core system is interested in the operations executed last
271         if (vnf.get().getOperationExecutions() != null) {
272             for (OperationExecution operationExecution : LifecycleChangeNotificationManager.NEWEST_OPERATIONS_FIRST.sortedCopy(vnf.get().getOperationExecutions())) {
273                 try {
274                     Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(operationExecution.getId(), NOKIA_LCM_API_VERSION);
275                     if (extractOnapJobId(operationParams).equals(jobId)) {
276                         return operationExecution;
277                     }
278                 } catch (ApiException e) {
279                     logger.error("Unable to retrieve operation parameters", e);
280                     throw new RuntimeException(e);
281                 }
282             }
283         }
284         throw new NoSuchElementException();
285     }
286
287     private Optional<VnfInfo> getVnf(String vnfmId, String vnfId) {
288         try {
289             //test if the VNF exists (required to be able to distingush between failed request )
290             VnfsApi cbamLcmApi = cbamRestApiProvider.getCbamLcmApi(vnfmId);
291             logger.debug("Listing VNFs");
292             List<VnfInfo> vnfs = cbamLcmApi.vnfsGet(NOKIA_LCM_API_VERSION);
293             com.google.common.base.Optional<VnfInfo> vnf = tryFind(vnfs, vnfInfo -> vnfId.equals(vnfInfo.getId()));
294             if (!vnf.isPresent()) {
295                 logger.debug("VNF with " + vnfId + " is missing");
296                 return empty();
297             } else {
298                 logger.debug("VNF with " + vnfId + " still exists");
299                 //query the VNF again to get operation execution result
300                 return of(cbamLcmApi.vnfsVnfInstanceIdGet(vnfId, NOKIA_LCM_API_VERSION));
301             }
302         } catch (ApiException e) {
303             logger.error("Unable to retrieve VNF", e);
304             throw new RuntimeException(e);
305         }
306     }
307
308 }