Fix container startup
[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.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;
27 import java.util.*;
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;
37
38 import static javax.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
39 import static java.util.Optional.empty;
40 import static java.util.Optional.of;
41
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;
55
56 /**
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
61  */
62 @Component
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>() {
66         @Override
67         public int compare(JobResponseInfo left, JobResponseInfo right) {
68             return Long.valueOf(left.getResponseId()).compareTo(Long.valueOf(right.getResponseId()));
69         }
70     };
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;
76
77     @Autowired
78     JobManager(CbamRestApiProvider cbamRestApiProvider, SelfRegistrationManager selfRegistrationManager) {
79         this.cbamRestApiProvider = cbamRestApiProvider;
80         this.selfRegistrationManager = selfRegistrationManager;
81     }
82
83     /**
84      * @param operationParams the operation execution
85      * @return the ONAP job identifier of belonging to the operation execution
86      */
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");
92         }
93         JsonElement jobId = additionalParams.getAsJsonObject().get("jobId");
94         if (jobId == null) {
95             throw new NoSuchElementException("The operation result " + operationParamsAsJson + " does not contain the mandatory jobId in the additionalParams structure");
96         }
97         return jobId.getAsString();
98     }
99
100     /**
101      * @return is the component preparing for shutdown
102      */
103     public boolean isPreparingForShutDown(){
104         return preparingForShutDown;
105     }
106
107     /**
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
110      *
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
114      */
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");
121             }
122             if (!selfRegistrationManager.isReady()) {
123                 response.setStatus(SC_SERVICE_UNAVAILABLE);
124                 throw buildFatalFailure(logger, "The service is not yet ready");
125             }
126         }
127         ongoingJobs.add(jobId);
128         return jobId;
129     }
130
131     /**
132      * Signal that a job has finished
133      *
134      * @param jobId the identifier of the job
135      */
136     public void jobFinished(String jobId) {
137         ongoingJobs.remove(jobId);
138     }
139
140     /**
141      * @return the system has any ongoing jobs
142      */
143     public boolean hasOngoingJobs() {
144         return !ongoingJobs.isEmpty();
145     }
146
147
148     /**
149      * Wait for all jobs to be cleared from the system the refuses to let additional request in
150      */
151     public void prepareForShutdown() {
152         preparingForShutDown = true;
153         while (true) {
154             synchronized (this) {
155                 if (!hasOngoingJobs()) {
156                     return;
157                 }
158             }
159             systemFunctions().sleep(500L);
160         }
161     }
162
163     /**
164      * @param vnfmId the identifier of the VNFM
165      * @param jobId  the identifier of the job
166      * @return detailed information of the job
167      */
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);
173         }
174         String vnfId = jobParts.get(0);
175         if (isEmpty(vnfId)) {
176             throw new IllegalArgumentException("The vnfId in the jobId (" + jobId + ") can not be empty");
177         }
178         String operationExecutionId = jobParts.get(1);
179         if (isEmpty(operationExecutionId)) {
180             throw new IllegalArgumentException("The UUID in the jobId (" + jobId + ") can not be empty");
181         }
182         Optional<VnfInfo> vnf = getVnf(vnfmId, vnfId);
183         if (!vnf.isPresent()) {
184             return getJobDetailInfoForMissingVnf(jobId);
185         } else {
186             return getJobInfoForExistingVnf(vnfmId, jobId, vnfId, vnf.get());
187         }
188     }
189
190     private JobDetailInfo getJobDetailInfoForMissingVnf(String jobId) {
191         if (ongoingJobs.contains(jobId)) {
192             return reportOngoing(jobId);
193         } else {
194             return reportFinished(jobId);
195         }
196     }
197
198     private JobDetailInfo getJobInfoForExistingVnf(String vnfmId, String jobId, String vnfId, VnfInfo vnf) {
199         try {
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);
206             } else {
207                 return reportFailed(jobId, "The requested operation was not able to start on CBAM");
208             }
209         }
210     }
211
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());
217         } else {
218             return getJobForTerminalOperationState(vnfmId, jobId, vnfId, operation);
219         }
220     }
221
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);
227             } else {
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");
231                 } else {
232                     return reportFinished(jobId);
233                 }
234             }
235         } else {
236             return reportFinished(jobId);
237         }
238     }
239
240     private JobDetailInfo buildJob(String jobId, JobResponseInfo... history) {
241         JobDetailInfo job = new JobDetailInfo();
242         job.setJobId(jobId);
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);
253         return job;
254     }
255
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);
262         return currentJob;
263     }
264
265     private JobDetailInfo reportOngoing(String jobId) {
266         return buildJob(jobId, buildJobPart(OPERATION_STARTED_DESCRIPTION, JobStatus.STARTED, 50, 1));
267     }
268
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)
273         );
274     }
275
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)
280         );
281     }
282
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));
290         }
291         throw new NoSuchElementException();
292     }
293
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
297             return false;
298         }
299         try {
300             Object operationParams = cbamOperationExecutionApi.operationExecutionsOperationExecutionIdOperationParamsGet(operationExecution.getId(), NOKIA_LCM_API_VERSION).blockingFirst();
301             if (extractOnapJobId(operationParams).equals(jobId)) {
302                 return true;
303             }
304         } catch (Exception e) {
305             throw buildFatalFailure(logger, "Unable to retrieve operation parameters of operation with " + operationExecution.getId() + " identifier", e);
306         }
307         return false;
308     }
309
310     private Optional<VnfInfo> getVnf(String vnfmId, String vnfId) {
311         try {
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);
319                 return empty();
320             } else {
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());
324             }
325         } catch (Exception e) {
326             throw buildFatalFailure(logger, "Unable to retrieve VNF with " + vnfId + " identifier", e);
327         }
328     }
329 }