2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 Intel Corp. All rights reserved.
6 * ================================================================================
7 * Modifications Copyright (c) 2019 Samsung
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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 * ============LICENSE_END=========================================================
23 package org.onap.so.bpmn.common.scripts
25 import com.fasterxml.jackson.databind.ObjectMapper
26 import org.camunda.bpm.engine.delegate.DelegateExecution
27 import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor
28 import org.onap.so.bpmn.common.scripts.ExceptionUtil
29 import org.onap.so.bpmn.common.util.OofInfraUtils
30 import org.onap.so.bpmn.core.UrnPropertiesReader
31 import org.onap.so.bpmn.core.domain.HomingSolution
32 import org.onap.so.bpmn.core.domain.ModelInfo
33 import org.onap.so.bpmn.core.domain.Resource
34 import org.onap.so.bpmn.core.domain.AllottedResource
35 import org.onap.so.bpmn.core.domain.ServiceDecomposition
36 import org.onap.so.bpmn.core.domain.ServiceInstance
37 import org.onap.so.bpmn.core.domain.Subscriber
38 import org.onap.so.bpmn.core.domain.VnfResource
39 import org.onap.so.bpmn.core.json.JsonUtils
40 import org.onap.so.client.HttpClient
41 import org.onap.so.client.HttpClientFactory
42 import org.onap.so.db.catalog.beans.CloudSite
43 import org.onap.so.db.catalog.beans.HomingInstance
44 import org.onap.logging.filter.base.ONAPComponents;
45 import org.springframework.http.HttpEntity
46 import org.springframework.http.HttpHeaders
47 import org.springframework.http.HttpMethod
48 import org.springframework.http.ResponseEntity
49 import org.springframework.http.client.BufferingClientHttpRequestFactory
50 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
51 import org.springframework.web.client.RestTemplate
52 import org.springframework.web.util.UriComponentsBuilder
53 import org.slf4j.Logger
54 import org.slf4j.LoggerFactory
56 import javax.ws.rs.core.MediaType
57 import javax.ws.rs.core.Response
58 import javax.ws.rs.core.UriBuilder
59 import javax.xml.ws.http.HTTPException
61 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
64 private static final Logger logger = LoggerFactory.getLogger( OofUtils.class);
66 ExceptionUtil exceptionUtil = new ExceptionUtil()
67 JsonUtils jsonUtil = new JsonUtils()
68 OofInfraUtils oofInfraUtils = new OofInfraUtils()
70 private AbstractServiceTaskProcessor utils
72 OofUtils(AbstractServiceTaskProcessor taskProcessor) {
73 this.utils = taskProcessor
77 * This method builds the service-agnostic
78 * OOF json request to get a homing solution
79 * and license solution
83 * @param decomposition - ServiceDecomposition object
84 * @param customerLocation -
85 * @param existingCandidates -
86 * @param excludedCandidates -
87 * @param requiredCandidates -
89 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
91 String buildRequest(DelegateExecution execution,
93 ServiceDecomposition decomposition,
94 Subscriber subscriber = null,
96 ArrayList existingCandidates = null,
97 ArrayList excludedCandidates = null,
98 ArrayList requiredCandidates = null) {
99 logger.debug( "Started Building OOF Request")
100 String callbackEndpoint = UrnPropertiesReader.getVariable("mso.oof.callbackEndpoint", execution)
101 logger.debug( "mso.oof.callbackEndpoint is: " + callbackEndpoint)
103 def callbackUrl = utils.createHomingCallbackURL(callbackEndpoint, "oofResponse", requestId)
104 logger.debug( "callbackUrl is: " + callbackUrl)
107 def transactionId = requestId
108 logger.debug( "transactionId is: " + transactionId)
109 //ServiceInstance Info
110 ServiceInstance serviceInstance = decomposition.getServiceInstance()
111 def serviceInstanceId = ""
114 serviceInstanceId = execution.getVariable("serviceInstanceId")
115 logger.debug( "serviceInstanceId is: " + serviceInstanceId)
116 serviceName = execution.getVariable("subscriptionServiceType")
117 logger.debug( "serviceName is: " + serviceName)
119 if (serviceInstanceId == null || serviceInstanceId == "null") {
120 logger.debug( "Unable to obtain Service Instance Id")
121 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
122 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
124 if (serviceName == null || serviceName == "null") {
125 logger.debug( "Unable to obtain Service Name")
126 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
127 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
130 ModelInfo model = decomposition.getModelInfo()
131 logger.debug( "ModelInfo: " + model.toString())
132 String modelType = model.getModelType()
133 String modelInvariantId = model.getModelInvariantUuid()
134 String modelVersionId = model.getModelUuid()
135 String modelName = model.getModelName()
136 String modelVersion = model.getModelVersion()
138 String subscriberId = ""
139 String subscriberName = ""
140 String commonSiteId = ""
141 if (subscriber != null) {
142 subscriberId = subscriber.getGlobalId()
143 subscriberName = subscriber.getName()
144 commonSiteId = subscriber.getCommonSiteId()
147 //Determine RequestType
148 //TODO Figure out better way to determine this
149 String requestType = "create"
150 List<Resource> resources = decomposition.getServiceResources()
151 for (Resource r : resources) {
152 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
153 if (currentSolution != null) {
154 requestType = "speed changed"
159 String placementDemands = ""
160 StringBuilder sb = new StringBuilder()
161 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
162 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
164 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
165 logger.debug( "Allotted Resources List is empty - will try to get service VNFs instead.")
167 for (AllottedResource resource : allottedResourceList) {
168 logger.debug( "Allotted Resource: " + resource.toString())
169 def serviceResourceId = resource.getResourceId()
170 def toscaNodeType = resource.getToscaNodeType()
171 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
172 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
173 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
174 def resourceModelName = resource.getModelInfo().getModelName()
175 def resourceModelVersion = resource.getModelInfo().getModelVersion()
176 def resourceModelType = resource.getModelInfo().getModelType()
177 def tenantId = execution.getVariable("tenantId")
178 def requiredCandidatesJson = ""
180 requiredCandidatesJson = createCandidateJson(
187 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
188 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
189 " \"tenantId\": \"${tenantId}\",\n" +
190 " \"resourceModelInfo\": {\n" +
191 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
192 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
193 " \"modelName\": \"${resourceModelName}\",\n" +
194 " \"modelType\": \"${resourceModelType}\",\n" +
195 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
196 " \"modelCustomizationName\": \"\"\n" +
197 " }" + requiredCandidatesJson + "\n" +
200 placementDemands = sb.append(demand)
204 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
205 logger.debug( "VNF Resources List is empty")
208 for (VnfResource vnfResource : vnfResourceList) {
209 logger.debug( "VNF Resource: " + vnfResource.toString())
210 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
211 def toscaNodeType = vnfResource.getToscaNodeType()
212 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
213 def serviceResourceId = vnfResource.getResourceId()
214 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
215 def resourceModelName = vnfResourceModelInfo.getModelName()
216 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
217 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
218 def resourceModelType = vnfResourceModelInfo.getModelType()
219 def tenantId = execution.getVariable("tenantId")
220 def requiredCandidatesJson = ""
223 String placementDemand =
225 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
226 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
227 " \"tenantId\": \"${tenantId}\",\n" +
228 " \"resourceModelInfo\": {\n" +
229 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
230 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
231 " \"modelName\": \"${resourceModelName}\",\n" +
232 " \"modelType\": \"${resourceModelType}\",\n" +
233 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
234 " \"modelCustomizationName\": \"\"\n" +
235 " }" + requiredCandidatesJson + "\n" +
238 placementDemands = sb.append(placementDemand)
240 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
243 /* Commenting Out Licensing as OOF doesn't support for Beijing
244 String licenseDemands = ""
245 sb = new StringBuilder()
246 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
247 logger.debug( "Vnf Resources List is Empty")
249 for (VnfResource vnfResource : vnfResourceList) {
250 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
251 def resourceInstanceType = vnfResource.getResourceType()
252 def serviceResourceId = vnfResource.getResourceId()
253 def resourceModuleName = vnfResource.getResourceType()
254 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
255 def resouceModelName = vnfResourceModelInfo.getModelName()
256 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
257 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
258 def resouceModelType = vnfResourceModelInfo.getModelType()
260 // TODO Add Existing Licenses to demand
261 //"existingLicenses": {
262 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
263 // "43257b49-9602-4fe5-9337-094e52bc9435"],
264 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
265 // "43257b49-9602-4fe5-9337-094e52bc9435"]
268 String licenseDemand =
270 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
271 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
272 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
273 "\"resourceModelInfo\": {\n" +
274 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
275 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
276 " \"modelName\": \"${resouceModelName}\",\n" +
277 " \"modelType\": \"${resouceModelType}\",\n" +
278 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
279 " \"modelCustomizationName\": \"\"\n" +
283 licenseDemands = sb.append(licenseDemand)
285 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
290 " \"requestInfo\": {\n" +
291 " \"transactionId\": \"${transactionId}\",\n" +
292 " \"requestId\": \"${requestId}\",\n" +
293 " \"callbackUrl\": \"${callbackUrl}\",\n" +
294 " \"sourceId\": \"so\",\n" +
295 " \"requestType\": \"${requestType}\"," +
296 " \"numSolutions\": 1,\n" +
297 " \"optimizers\": [\"placement\"],\n" +
298 " \"timeout\": 600\n" +
300 " \"placementInfo\": {\n" +
301 " \"requestParameters\": {\n" +
302 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
303 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
304 " \"customerName\": \"${customerLocation.customerName}\"\n" +
306 " \"subscriberInfo\": { \n" +
307 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
308 " \"subscriberName\": \"${subscriberName}\",\n" +
309 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
311 " \"placementDemands\": [\n" +
312 " ${placementDemands}\n" +
315 " \"serviceInfo\": {\n" +
316 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
317 " \"serviceName\": \"${serviceName}\",\n" +
318 " \"modelInfo\": {\n" +
319 " \"modelType\": \"${modelType}\",\n" +
320 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
321 " \"modelVersionId\": \"${modelVersionId}\",\n" +
322 " \"modelName\": \"${modelName}\",\n" +
323 " \"modelVersion\": \"${modelVersion}\",\n" +
324 " \"modelCustomizationName\": \"\"\n" +
330 logger.debug( "Completed Building OOF Request")
332 } catch (Exception ex) {
333 logger.debug( "buildRequest Exception: " + ex)
338 * This method validates the callback response
339 * from OOF. If the response contains an
340 * exception the method will build and throw
341 * a workflow exception.
344 * @param response - the async callback response from oof
346 Void validateCallbackResponse(DelegateExecution execution, String response) {
347 String placements = ""
348 if (isBlank(response)) {
349 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
351 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
352 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
353 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
354 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
355 if (isBlank(statusMessage)) {
356 logger.debug( "Error Occurred in Homing: OOF Async Callback Response does " +
357 "not contain placement solution.")
358 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
359 "OOF Async Callback Response does not contain placement solution.")
361 logger.debug( "Error Occurred in Homing: " + statusMessage)
362 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
367 } else if (response.contains("error") || response.contains("Error") ) {
368 String errorMessage = ""
369 if (response.contains("policyException")) {
370 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
371 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
372 } else if (response.contains("Unable to find any candidate for demand")) {
373 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
374 "demand *** Response: " + response.toString()
375 } else if (response.contains("serviceException")) {
376 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
377 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
379 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
381 logger.debug( "Error Occurred in Homing: " + errorMessage)
382 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
385 logger.debug( "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.")
386 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
393 * This method creates candidates json for placement Demands.
396 * @param existingCandidates -
397 * @param excludedCandidates -
398 * @param requiredCandidates -
400 * @return candidatesJson - a JSON string with candidates
402 String createCandidateJson(ArrayList existingCandidates = null,
403 ArrayList excludedCandidates = null,
404 ArrayList requiredCandidates = null) {
405 def candidatesJson = ""
407 if (existingCandidates != null && existingCandidates != {}) {
408 sb = new StringBuilder()
410 " \"existingCandidates\": [\n")
411 def existingCandidateJson = ""
412 existingCandidates.each { existingCandidate ->
413 type = existingCandidate.get('identifierType')
414 if (type == 'vimId') {
415 def cloudOwner = existingCandidate.get('cloudOwner')
416 def cloudRegionId = existingCandidate.get('identifiers')
417 existingCandidateJson = "{\n" +
418 " \"identifierType\": \"vimId\",\n" +
419 " \"cloudOwner\": \"${cloudOwner}\",\n" +
420 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
422 sb.append(existingCandidateJson)
424 if (type == 'serviceInstanceId') {
425 def serviceInstanceId = existingCandidate.get('identifiers')
426 existingCandidateJson += "{\n" +
427 " \"identifierType\": \"serviceInstanceId\",\n" +
428 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
430 sb.append(existingCandidateJson)
433 if (existingCandidateJson != "") {
434 sb.setLength(sb.length() - 1)
435 candidatesJson = sb.append(",\n],")
438 if (excludedCandidates != null && excludedCandidates != {}) {
439 sb = new StringBuilder()
441 " \"excludedCandidates\": [\n")
442 def excludedCandidateJson = ""
443 excludedCandidates.each { excludedCandidate ->
444 type = excludedCandidate.get('identifierType')
445 if (type == 'vimId') {
446 def cloudOwner = excludedCandidate.get('cloudOwner')
447 def cloudRegionId = excludedCandidate.get('identifiers')
448 excludedCandidateJson = "{\n" +
449 " \"identifierType\": \"vimId\",\n" +
450 " \"cloudOwner\": \"${cloudOwner}\",\n" +
451 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
453 sb.append(excludedCandidateJson)
455 if (type == 'serviceInstanceId') {
456 def serviceInstanceId = excludedCandidate.get('identifiers')
457 excludedCandidateJson += "{\n" +
458 " \"identifierType\": \"serviceInstanceId\",\n" +
459 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
461 sb.append(excludedCandidateJson)
464 if (excludedCandidateJson != "") {
465 sb.setLength(sb.length() - 1)
466 candidatesJson = sb.append(",\n],")
469 if (requiredCandidates != null && requiredCandidates != {}) {
470 sb = new StringBuilder()
472 " \"requiredCandidates\": [\n")
473 def requiredCandidatesJson = ""
474 requiredCandidates.each { requiredCandidate ->
475 type = requiredCandidate.get('identifierType')
476 if (type == 'vimId') {
477 def cloudOwner = requiredCandidate.get('cloudOwner')
478 def cloudRegionId = requiredCandidate.get('identifiers')
479 requiredCandidatesJson = "{\n" +
480 " \"identifierType\": \"vimId\",\n" +
481 " \"cloudOwner\": \"${cloudOwner}\",\n" +
482 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
484 sb.append(requiredCandidatesJson)
486 if (type == 'serviceInstanceId') {
487 def serviceInstanceId = requiredCandidate.get('identifiers')
488 requiredCandidatesJson += "{\n" +
489 " \"identifierType\": \"serviceInstanceId\",\n" +
490 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
492 sb.append(requiredCandidatesJson)
495 if (requiredCandidatesJson != "") {
496 sb.setLength(sb.length() - 1)
497 candidatesJson = sb.append(",\n],")
500 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
501 return candidatesJson
505 * This method creates a cloudsite in catalog database.
507 * @param CloudSite cloudSite
511 Void createCloudSite(CloudSite cloudSite, DelegateExecution execution) {
512 oofInfraUtils.createCloudSite(cloudSite, execution)
516 * This method creates a HomingInstance in catalog database.
518 * @param HomingInstance homingInstance
522 Void createHomingInstance(HomingInstance homingInstance, DelegateExecution execution) {
523 oofInfraUtils.createHomingInstance(homingInstance, execution)
526 String getMsbHost(DelegateExecution execution) {
527 String msbHost = UrnPropertiesReader.getVariable("mso.msb.host", execution, "msb-iag.onap")
529 Integer msbPort = UrnPropertiesReader.getVariable("mso.msb.port", execution, "80").toInteger()
531 return UriBuilder.fromPath("").host(msbHost).port(msbPort).scheme("http").build().toString()
534 public String buildSelectNSTRequest(String requestId, Map<String, Object> profileInfo) {
535 def transactionId = requestId
536 logger.debug( "transactionId is: " + transactionId)
537 String callbackUrl = "http://0.0.0.0:9000/callback/"
538 ObjectMapper objectMapper = new ObjectMapper()
539 String json = objectMapper.writeValueAsString(profileInfo)
540 StringBuilder response = new StringBuilder()
543 " \"requestInfo\": {\n" +
544 " \"transactionId\": \"${transactionId}\",\n" +
545 " \"requestId\": \"${requestId}\",\n" +
546 " \"sourceId\": \"so\",\n" +
547 " \"timeout\": 600,\n" +
548 " \"callbackUrl\": \"${callbackUrl}\"\n" +
550 response.append(" \"serviceProfile\": {\n" +
551 " \"serviceProfileParameters\": ")
552 response.append(json);
553 response.append("\n }\n")
554 response.append("\n}\n")
555 return response.toString()
558 public String buildSelectNSIRequest(String requestId, String nstInfo, Map<String, Object> profileInfo){
560 def transactionId = requestId
561 logger.debug( "transactionId is: " + transactionId)
562 String callbackUrl = "http://0.0.0.0:9000/callback/"
563 ObjectMapper objectMapper = new ObjectMapper();
564 String json = objectMapper.writeValueAsString(profileInfo);
565 StringBuilder response = new StringBuilder();
568 " \"requestInfo\": {\n" +
569 " \"transactionId\": \"${transactionId}\",\n" +
570 " \"requestId\": \"${requestId}\",\n" +
571 " \"sourceId\": \"so\",\n" +
572 " \"timeout\": 600,\n" +
573 " \"callbackUrl\": \"${callbackUrl}\"\n" +
575 " \"serviceInfo\": {\n" +
576 " \"serviceInstanceId\": \"\",\n" +
577 " \"serviceName\": \"\"\n" +
579 " \"NSTInfoList\": [\n")
580 response.append(nstInfo);
581 response.append("\n ],\n")
582 response.append("\n \"serviceProfile\": \n")
583 response.append(json);
584 response.append("\n }\n")
585 return response.toString()