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