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