Include impacted changes for APPC-346,APPC-348
[appc.git] / appc-dispatcher / appc-request-handler / appc-request-handler-core / src / main / java / org / onap / appc / requesthandler / impl / RequestValidatorImpl.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP : APPC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Copyright (C) 2017 Amdocs
8  * =============================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  * 
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  * 
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * 
21  * ECOMP is a trademark and service mark of AT&T Intellectual Property.
22  * ============LICENSE_END=========================================================
23  */
24
25 package org.onap.appc.requesthandler.impl;
26
27 import com.att.eelf.i18n.EELFResourceManager;
28 import com.fasterxml.jackson.databind.JsonNode;
29 import com.fasterxml.jackson.databind.ObjectMapper;
30 import com.fasterxml.jackson.databind.node.ObjectNode;
31
32 import jline.internal.Log;
33
34 import org.apache.commons.io.IOUtils;
35 import org.apache.commons.lang.ObjectUtils;
36 import org.apache.commons.lang.StringUtils;
37 import org.apache.http.HttpResponse;
38 import org.json.JSONObject;
39 import org.onap.appc.domainmodel.lcm.Flags;
40 import org.onap.appc.domainmodel.lcm.RequestContext;
41 import org.onap.appc.domainmodel.lcm.RuntimeContext;
42 import org.onap.appc.domainmodel.lcm.TransactionRecord;
43 import org.onap.appc.domainmodel.lcm.VNFContext;
44 import org.onap.appc.domainmodel.lcm.VNFOperation;
45 import org.onap.appc.exceptions.APPCException;
46 import org.onap.appc.executor.objects.LCMCommandStatus;
47 import org.onap.appc.executor.objects.Params;
48 import org.onap.appc.i18n.Msg;
49 import org.onap.appc.lockmanager.api.LockException;
50 import org.onap.appc.lockmanager.api.LockManager;
51 import org.onap.appc.logging.LoggingConstants;
52 import org.onap.appc.logging.LoggingUtils;
53 import org.onap.appc.requesthandler.exceptions.DGWorkflowNotFoundException;
54 import org.onap.appc.requesthandler.exceptions.LCMOperationsDisabledException;
55 import org.onap.appc.requesthandler.exceptions.MissingVNFDataInAAIException;
56 import org.onap.appc.requesthandler.exceptions.RequestValidationException;
57 import org.onap.appc.requesthandler.exceptions.VNFNotFoundException;
58 import org.onap.appc.requesthandler.exceptions.WorkflowNotFoundException;
59 import org.onap.appc.requesthandler.model.ActionIdentifierModel;
60 import org.onap.appc.requesthandler.model.Input;
61 import org.onap.appc.requesthandler.model.Request;
62 import org.onap.appc.requesthandler.model.RequestData;
63 import org.onap.appc.requesthandler.model.RequestModel;
64 import org.onap.appc.requesthandler.model.ScopeOverlapModel;
65 import org.onap.appc.rest.client.RestClientInvoker;
66 import org.onap.appc.validationpolicy.RequestValidationPolicy;
67 import org.onap.appc.validationpolicy.executors.RuleExecutor;
68 import org.onap.appc.validationpolicy.objects.RuleResult;
69 import org.onap.appc.workflow.WorkFlowManager;
70 import org.onap.appc.workflow.objects.WorkflowExistsOutput;
71 import org.onap.appc.workflow.objects.WorkflowRequest;
72 import org.onap.ccsdk.sli.core.sli.SvcLogicContext;
73 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
74 import org.onap.ccsdk.sli.core.sli.SvcLogicResource;
75 import org.onap.ccsdk.sli.adaptors.aai.AAIService;
76 import org.osgi.framework.BundleContext;
77 import org.osgi.framework.FrameworkUtil;
78 import org.osgi.framework.ServiceReference;
79
80 import java.io.IOException;
81 import java.net.MalformedURLException;
82 import java.net.URL;
83 import java.util.ArrayList;
84 import java.util.Date;
85 import java.util.List;
86 import java.util.Properties;
87 import java.util.stream.Collectors;
88
89 public class RequestValidatorImpl extends AbstractRequestValidatorImpl {
90
91     private WorkFlowManager workflowManager;
92     private LockManager lockManager;
93     private AAIService aaiService;
94     private RequestValidationPolicy requestValidationPolicy;
95     private RestClientInvoker client;
96     private String path;
97
98     static final String SCOPE_OVERLAP_ENDPOINT = "appc.LCM.scopeOverlap.endpoint";
99     static final String ODL_USER = "appc.LCM.provider.user";
100     static final String ODL_PASS = "appc.LCM.provider.pass";
101     
102     public void initialize() throws APPCException {
103         logger.info("Initializing RequestValidatorImpl.");
104         String endpoint = null;
105         String user = null;
106         String pass =null;
107         Properties properties = configuration.getProperties();
108         if (properties != null) {
109             endpoint = properties.getProperty(SCOPE_OVERLAP_ENDPOINT);
110             user = properties.getProperty(ODL_USER);
111             pass = properties.getProperty(ODL_PASS);
112         }
113         if (endpoint == null) {
114             String message = "End point is not defined for scope over lap service in appc.properties.";
115             logger.error(message);
116             // TODO throw following exception (and remove the "return") when
117             // entry in appc.properties file is made for scope overlap service
118             // endpoint
119             // and remove @Ignore in unit tests:
120             // testInitializeWithNullConfigProps,
121             // testInitializeWithoutEndpointProp
122             // throw new APPCException(message);
123             return;
124         }
125
126         try {
127             URL url = new URL(endpoint);
128             client = new RestClientInvoker(url);
129             client.setAuthentication(user, pass);
130             path = url.getPath();    
131             
132         } catch (MalformedURLException e) {
133             String message = "Invalid endpoint " + endpoint;
134             logger.error(message, e);
135             // TODO throw following exception when entry in appc.properties file
136             // is made for scope overlap service endpoint
137             // and remove @Ignore in unit test:
138             // testInitializeWithMalFormatEndpoint
139             // throw new APPCException(message);
140         }
141     }
142
143     private void getAAIservice() {
144         BundleContext bctx = FrameworkUtil.getBundle(AAIService.class).getBundleContext();
145         // Get AAIadapter reference
146         ServiceReference sref = bctx.getServiceReference(AAIService.class.getName());
147         if (sref != null) {
148             logger.info("AAIService from bundlecontext");
149             aaiService = (AAIService) bctx.getService(sref);
150
151         } else {
152             logger.error("Cannot find service reference for org.onap.ccsdk.sli.adaptors.aai.AAIService");
153
154         }
155     }
156
157     public void setLockManager(LockManager lockManager) {
158         this.lockManager = lockManager;
159     }
160
161     public void setClient(RestClientInvoker client) {
162         this.client = client;
163     }
164
165     public void setWorkflowManager(WorkFlowManager workflowManager) {
166         this.workflowManager = workflowManager;
167     }
168
169     public void setRequestValidationPolicy(RequestValidationPolicy requestValidationPolicy) {
170         this.requestValidationPolicy = requestValidationPolicy;
171     }
172
173     public void validateRequest(RuntimeContext runtimeContext) throws Exception {
174         if (logger.isTraceEnabled()) {
175             logger.trace(
176                     "Entering to validateRequest with RequestHandlerInput = " + ObjectUtils.toString(runtimeContext));
177         }
178         if (!lcmStateManager.isLCMOperationEnabled()) {
179             LoggingUtils.logErrorMessage(LoggingConstants.TargetNames.REQUEST_VALIDATOR,
180                     EELFResourceManager.format(Msg.LCM_OPERATIONS_DISABLED), this.getClass().getCanonicalName());
181             throw new LCMOperationsDisabledException("APPC LCM operations have been administratively disabled");
182         }
183
184         getAAIservice();
185         validateInput(runtimeContext);
186         String vnfId = runtimeContext.getRequestContext().getActionIdentifiers().getVnfId();
187         VNFContext vnfContext = queryAAI(vnfId);
188         runtimeContext.setVnfContext(vnfContext);
189         runtimeContext.getTransactionRecord().setTargetType(vnfContext.getType());
190
191         VNFOperation operation = runtimeContext.getRequestContext().getAction();
192         if (operation.isBuiltIn()) {
193             return;
194         }
195
196         validateVNFLock(runtimeContext);
197         checkWorkflowExists(vnfContext, runtimeContext.getRequestContext());
198
199         if (runtimeContext.getRequestContext().getCommonHeader().getFlags().isForce()) {
200             return;
201         }
202
203         List<TransactionRecord> inProgressTransactions = transactionRecorder
204                 .getInProgressRequests(runtimeContext.getTransactionRecord());
205         logger.debug("In progress requests " + inProgressTransactions.toString());
206
207         Long exclusiveRequestCount = inProgressTransactions.stream()
208                 .filter(record -> record.getMode().equals(Flags.Mode.EXCLUSIVE.name())).count();
209         if (exclusiveRequestCount > 0) {
210             String message = "Request rejected - Existing request in progress with exclusive mode for VNF: " + vnfId;
211             throw new RequestValidationException(message, LCMCommandStatus.EXLCUSIVE_REQUEST_IN_PROGRESS,
212                     (new Params()).addParam("errorMsg", message));
213         }
214
215         Boolean scopeOverLap = checkScopeOverLap(runtimeContext.getRequestContext(), inProgressTransactions);
216         logger.debug("Scope overlap " + scopeOverLap);
217         if (scopeOverLap) {
218             List<VNFOperation> inProgressActions = inProgressTransactions.stream().map(TransactionRecord::getOperation)
219                     .collect(Collectors.toList());
220
221             RuleExecutor ruleExecutor = requestValidationPolicy.getInProgressRuleExecutor();
222             RuleResult result = ruleExecutor.executeRule(operation.name(), inProgressActions);
223             logger.debug("Policy validation result " + result);
224             if (RuleResult.REJECT == result) {
225                 String message = "Request rejected as per the request validation policy";
226                 throw new RequestValidationException(message, LCMCommandStatus.POLICY_VALIDATION_FAILURE,
227                         (new Params()).addParam("errorMsg", message));
228             }
229         }
230     }
231
232     private void validateVNFLock(RuntimeContext runtimeContext) throws LockException {
233         String vnfId = runtimeContext.getRequestContext().getActionIdentifiers().getVnfId();
234         String lockOwner = lockManager.getLockOwner(vnfId);
235         logger.debug("Current lock owner is " + lockOwner + " for vnf " + vnfId);
236         if (lockOwner != null
237                 && !lockOwner.equals(runtimeContext.getRequestContext().getCommonHeader().getRequestId())) {
238             String message = new StringBuilder("VNF : ").append(vnfId).append(" is locked by request id :")
239                     .append(lockOwner).toString();
240             throw new LockException(message);
241         }
242     }
243
244     /*
245      * Do not remove this method, this is actual method for invoking scope
246      * overlap service When the service becomes available, its dummy
247      * implementation should be removed and this implementation should be used.
248      */
249     private Boolean checkScopeOverLap(RequestContext requestContext, List<TransactionRecord> inProgressTransactions)
250             throws APPCException {
251         Boolean scopeOverlap = null;
252         try {
253             JsonNode inputJSON = convertToJsonInput(requestContext, inProgressTransactions);
254             logger.debug("Input to scope overlap service " + inputJSON.toString());
255             HttpResponse response = client.doPost(path, inputJSON.toString());
256             int httpCode = response.getStatusLine().getStatusCode();
257             if (httpCode < 200 || httpCode >= 300) {
258                 logger.debug("http error code " + httpCode);
259                 throw new APPCException("Exception occurred on invoking check scope overlap api");
260             }
261             String respBody = IOUtils.toString(response.getEntity().getContent());
262             logger.debug("response body " + respBody);
263             ObjectMapper mapper = new ObjectMapper();
264             JsonNode outputJSON = mapper.readTree(respBody);
265             scopeOverlap = readScopeOverlap(outputJSON);
266         } catch (IOException e) {
267             String message = "Error accessing check scope overlap service";
268             logger.error(message, e);
269             throw new APPCException(message);
270         }
271         return scopeOverlap;
272     }
273
274     private Boolean readScopeOverlap(JsonNode outputJSON) throws APPCException {
275         logger.debug("Response JSON " + outputJSON.toString());
276         String message = "Error reading response JSON from scope overlap service ";
277         JsonNode outputNode = outputJSON.get("output");
278         JsonNode statusNode = outputNode.get("status");
279         if (statusNode == null) {
280             throw new APPCException(message);
281         }
282
283         if (null == statusNode.get("message"))
284             throw new APPCException(message + "Status message is null.");
285         String responseStatusMessage = statusNode.get("message").textValue();
286
287         if (null == statusNode.get("code"))
288             throw new APPCException(message + "Status code is null.");
289         String code = statusNode.get("code").textValue();
290
291         JsonNode responseInfoNode = outputNode.get("response-info");
292         JsonNode blockNode = responseInfoNode.get("block");
293         String requestOverlapValue = null;
294
295         if (null != blockNode)
296             requestOverlapValue = blockNode.textValue();
297
298         logger.debug("Response JSON " + requestOverlapValue);
299
300         if (code.equals("400")) {
301             if(null==requestOverlapValue)
302                 throw new APPCException("Response code is 400 but requestOverlapValue is null ");
303             if (requestOverlapValue.contains("true")) {
304                 return Boolean.TRUE;
305             } else if (requestOverlapValue.contains("false")) {
306                 return Boolean.FALSE;
307             } else {
308                 throw new APPCException(
309                         message + "requestOverlap value is other than True and False, it is " + requestOverlapValue);
310             }
311         } else if (code.equals("401")) {
312             throw new APPCException(message + responseStatusMessage);
313         } else {
314             throw new APPCException(message + "Status code is neither 400 nor 401, it is " + code);
315         }
316
317     }
318
319     private JsonNode convertToJsonInput(RequestContext requestContext, List<TransactionRecord> inProgressTransactions) {
320         ObjectMapper objectMapper = new ObjectMapper();
321         ScopeOverlapModel scopeOverlapModel = getScopeOverlapModel(requestContext, inProgressTransactions);
322         // Added for change in interface for action level
323         
324         JsonNode jsonObject = objectMapper.valueToTree(scopeOverlapModel);
325
326         return jsonObject;
327     }
328
329     public ScopeOverlapModel getScopeOverlapModel(RequestContext requestContext,
330             List<TransactionRecord> inProgressTransactions) {
331         ScopeOverlapModel scopeOverlapModel = new ScopeOverlapModel();
332         RequestData requestData = new RequestData();
333
334         List<RequestModel> inProgressRequests = new ArrayList<>();
335         RequestModel requestModel = new RequestModel();
336         ActionIdentifierModel actionIdentifierModel = extractActionIdentifierModel(requestContext);
337         requestModel.setAction(requestContext.getAction().toString());
338         requestModel.setActionIdentifier(actionIdentifierModel);
339
340         if (requestModel.getActionIdentifier().getVnfId() != null) {
341             requestData.setVnfID(requestModel.getActionIdentifier().getVnfId());
342         }
343
344         if (requestModel.getActionIdentifier().getVnfcName() != null
345                 || requestModel.getActionIdentifier().getVfModuleId() != null
346                 || requestModel.getActionIdentifier().getVserverId() != null) {
347
348             requestModel.getActionIdentifier().setVnfId(null);
349         }
350
351         requestData.setCurrentRequest(requestModel);
352
353         for (TransactionRecord record : inProgressTransactions) {
354             RequestModel request = new RequestModel();
355             ActionIdentifierModel actionIdentifier = new ActionIdentifierModel();
356
357             actionIdentifier.setServiceInstanceId(record.getServiceInstanceId());
358             actionIdentifier.setVnfId(record.getTargetId());
359             actionIdentifier.setVnfcName(record.getVnfcName());
360             actionIdentifier.setVfModuleId(record.getVfModuleId());
361             actionIdentifier.setVserverId(record.getVserverId());
362
363             request.setAction(record.getOperation().name());
364             request.setActionIdentifier(actionIdentifier);
365             if (request.getActionIdentifier().getVnfcName() != null
366                         || request.getActionIdentifier().getVfModuleId() != null
367                         || request.getActionIdentifier().getVserverId() != null) {
368
369                     request.getActionIdentifier().setVnfId(null);
370             }
371             inProgressRequests.add(request);
372         }
373
374         requestData.setInProgressRequests(inProgressRequests);
375
376         Request request = new Request();
377
378         Date date = new Date();
379         request.setRequestID("RequestId-ScopeOverlap " + date.toString()); 
380         request.setAction("isScopeOverlap");
381         ObjectMapper objectMapper = new ObjectMapper();
382         JsonNode json = objectMapper.valueToTree(requestData);
383         request.setRequestData(json.toString());
384         Input input = new Input();
385         input.setRequest(request);
386         scopeOverlapModel.setInput(input);
387         
388         return scopeOverlapModel;
389     }
390
391     private ActionIdentifierModel extractActionIdentifierModel(RequestContext requestContext) {
392         ActionIdentifierModel actionIdentifierModel = new ActionIdentifierModel();
393         actionIdentifierModel.setServiceInstanceId(requestContext.getActionIdentifiers().getServiceInstanceId());
394         actionIdentifierModel.setVnfId(requestContext.getActionIdentifiers().getVnfId());
395         actionIdentifierModel.setVnfcName(requestContext.getActionIdentifiers().getVnfcName());
396         actionIdentifierModel.setVfModuleId(requestContext.getActionIdentifiers().getVfModuleId());
397         actionIdentifierModel.setVserverId(requestContext.getActionIdentifiers().getVserverId());
398         return actionIdentifierModel;
399     }
400
401     private VNFContext queryAAI(String vnfId) throws VNFNotFoundException, MissingVNFDataInAAIException {
402         SvcLogicContext ctx = new SvcLogicContext();
403         ctx = getVnfdata(vnfId, "vnf", ctx);
404
405         VNFContext vnfContext = new VNFContext();
406         populateVnfContext(vnfContext, ctx);
407
408         return vnfContext;
409     }
410
411     private SvcLogicContext getVnfdata(String vnf_id, String prefix, SvcLogicContext ctx) throws VNFNotFoundException {
412         if (logger.isTraceEnabled()) {
413             logger.trace("Entering to getVnfdata with vnfid = " + ObjectUtils.toString(vnf_id) + ", prefix = "
414                     + ObjectUtils.toString(prefix) + ", SvcLogicContext" + ObjectUtils.toString(ctx));
415         }
416         String key = "vnf-id = '" + vnf_id + "'";
417         logger.debug("inside getVnfdata=== " + key);
418         try {
419             Date beginTimestamp = new Date();
420             SvcLogicResource.QueryStatus response = aaiService.query("generic-vnf", false, null, key, prefix, null,
421                     ctx);
422             Date endTimestamp = new Date();
423             String status = SvcLogicResource.QueryStatus.SUCCESS.equals(response)
424                     ? LoggingConstants.StatusCodes.COMPLETE : LoggingConstants.StatusCodes.ERROR;
425             LoggingUtils.logMetricsMessage(beginTimestamp.toInstant(), endTimestamp.toInstant(),
426                     LoggingConstants.TargetNames.AAI, LoggingConstants.TargetServiceNames.AAIServiceNames.QUERY, status,
427                     "", response.name(), this.getClass().getCanonicalName());
428             if (SvcLogicResource.QueryStatus.NOT_FOUND.equals(response)) {
429                 throw new VNFNotFoundException("VNF not found for vnf_id = " + vnf_id, vnf_id);
430             } else if (SvcLogicResource.QueryStatus.FAILURE.equals(response)) {
431                 throw new RuntimeException("Error Querying AAI with vnfID = " + vnf_id);
432             }
433             logger.info("AAIResponse: " + response.toString());
434         } catch (SvcLogicException e) {
435
436             LoggingUtils.logErrorMessage(LoggingConstants.TargetServiceNames.AAIServiceNames.GET_VNF_DATA,
437                     "Error in getVnfdata" + e, this.getClass().getCanonicalName());
438
439             throw new RuntimeException(e);
440         }
441         if (logger.isTraceEnabled()) {
442             logger.trace("Exiting from getVnfdata with (SvcLogicContext = " + ObjectUtils.toString(ctx) + ")");
443         }
444         return ctx;
445     }
446
447     private void populateVnfContext(VNFContext vnfContext, SvcLogicContext ctx) throws MissingVNFDataInAAIException {
448         String vnfType = ctx.getAttribute("vnf.vnf-type");
449         if (StringUtils.isEmpty(vnfType)) {
450             throw new MissingVNFDataInAAIException("vnf-type", ctx.getAttribute("vnf.vnf-id"));
451         }
452         vnfContext.setType(vnfType);
453         vnfContext.setId(ctx.getAttribute("vnf.vnf-id"));
454     }
455
456     private void checkWorkflowExists(VNFContext vnfContext, RequestContext requestContext)
457             throws WorkflowNotFoundException, DGWorkflowNotFoundException {
458         WorkflowExistsOutput workflowExistsOutput = workflowManager
459                 .workflowExists(getWorkflowQueryParams(vnfContext, requestContext));
460         if (!workflowExistsOutput.isMappingExist()) {
461             if (logger.isDebugEnabled()) {
462                 logger.debug("WorkflowManager : Workflow mapping not found for vnfType = " + vnfContext.getType()
463                         + ", version = " + vnfContext.getVersion() + ", command = "
464                         + requestContext.getAction().name());
465             }
466             LoggingUtils.logErrorMessage(LoggingConstants.TargetNames.WORKFLOW_MANAGER, EELFResourceManager
467                     .format(Msg.APPC_WORKFLOW_NOT_FOUND, vnfContext.getType(), requestContext.getAction().name()),
468                     this.getClass().getCanonicalName());
469             throw new WorkflowNotFoundException(
470                     "Workflow mapping not found for vnfType = " + vnfContext.getType() + ", command = "
471                             + requestContext.getAction().name(),
472                     vnfContext.getType(), requestContext.getAction().name());
473         }
474         if (!workflowExistsOutput.isDgExist()) {
475             if (logger.isDebugEnabled()) {
476                 logger.debug("WorkflowManager : DG Workflow not found for vnfType = " + vnfContext.getType()
477                         + ", version = " + vnfContext.getVersion() + ", command = " + requestContext.getAction().name()
478                         + " " + workflowExistsOutput);
479             }
480             LoggingUtils.logErrorMessage(LoggingConstants.TargetNames.WORKFLOW_MANAGER, EELFResourceManager
481                     .format(Msg.APPC_WORKFLOW_NOT_FOUND, vnfContext.getType(), requestContext.getAction().name()),
482                     this.getClass().getCanonicalName());
483             throw new DGWorkflowNotFoundException(
484                     "Workflow not found for vnfType = " + vnfContext.getType() + ", command = "
485                             + requestContext.getAction().name(),
486                     workflowExistsOutput.getWorkflowModule(), workflowExistsOutput.getWorkflowName(),
487                     workflowExistsOutput.getWorkflowVersion(), vnfContext.getType(), requestContext.getAction().name());
488         }
489     }
490
491     private WorkflowRequest getWorkflowQueryParams(VNFContext vnfContext, RequestContext requestContext) {
492         WorkflowRequest workflowRequest = new WorkflowRequest();
493         workflowRequest.setVnfContext(vnfContext);
494         workflowRequest.setRequestContext(requestContext);
495         if (logger.isTraceEnabled()) {
496             logger.trace("Exiting from getWorkflowQueryParams with (WorkflowRequest = "
497                     + ObjectUtils.toString(workflowRequest) + ")");
498         }
499         return workflowRequest;
500     }
501 }