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 org.camunda.bpm.engine.delegate.DelegateExecution
26 import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor
27 import org.onap.so.bpmn.common.scripts.ExceptionUtil
28 import org.onap.so.bpmn.common.util.OofInfraUtils
29 import org.onap.so.bpmn.core.UrnPropertiesReader
30 import org.onap.so.bpmn.core.domain.HomingSolution
31 import org.onap.so.bpmn.core.domain.ModelInfo
32 import org.onap.so.bpmn.core.domain.Resource
33 import org.onap.so.bpmn.core.domain.AllottedResource
34 import org.onap.so.bpmn.core.domain.ServiceDecomposition
35 import org.onap.so.bpmn.core.domain.ServiceInstance
36 import org.onap.so.bpmn.core.domain.Subscriber
37 import org.onap.so.bpmn.core.domain.VnfResource
38 import org.onap.so.bpmn.core.json.JsonUtils
39 import org.onap.so.client.HttpClient
40 import org.onap.so.client.HttpClientFactory
41 import org.onap.so.db.catalog.beans.CloudSite
42 import org.onap.so.db.catalog.beans.HomingInstance
43 import org.onap.so.utils.TargetEntity
44 import org.springframework.http.HttpEntity
45 import org.springframework.http.HttpHeaders
46 import org.springframework.http.HttpMethod
47 import org.springframework.http.ResponseEntity
48 import org.springframework.http.client.BufferingClientHttpRequestFactory
49 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
50 import org.springframework.web.client.RestTemplate
51 import org.springframework.web.util.UriComponentsBuilder
52 import org.slf4j.Logger
53 import org.slf4j.LoggerFactory
55 import javax.ws.rs.core.MediaType
56 import javax.ws.rs.core.Response
57 import javax.ws.rs.core.UriBuilder
58 import javax.xml.ws.http.HTTPException
60 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
63 private static final Logger logger = LoggerFactory.getLogger( OofUtils.class);
65 ExceptionUtil exceptionUtil = new ExceptionUtil()
66 JsonUtils jsonUtil = new JsonUtils()
67 OofInfraUtils oofInfraUtils = new OofInfraUtils()
69 private AbstractServiceTaskProcessor utils
71 OofUtils(AbstractServiceTaskProcessor taskProcessor) {
72 this.utils = taskProcessor
76 * This method builds the service-agnostic
77 * OOF json request to get a homing solution
78 * and license solution
82 * @param decomposition - ServiceDecomposition object
83 * @param customerLocation -
84 * @param existingCandidates -
85 * @param excludedCandidates -
86 * @param requiredCandidates -
88 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
90 String buildRequest(DelegateExecution execution,
92 ServiceDecomposition decomposition,
93 Subscriber subscriber = null,
95 ArrayList existingCandidates = null,
96 ArrayList excludedCandidates = null,
97 ArrayList requiredCandidates = null) {
98 logger.debug( "Started Building OOF Request")
99 String callbackEndpoint = UrnPropertiesReader.getVariable("mso.oof.callbackEndpoint", execution)
100 logger.debug( "mso.oof.callbackEndpoint is: " + callbackEndpoint)
102 def callbackUrl = utils.createHomingCallbackURL(callbackEndpoint, "oofResponse", requestId)
103 logger.debug( "callbackUrl is: " + callbackUrl)
106 def transactionId = requestId
107 logger.debug( "transactionId is: " + transactionId)
108 //ServiceInstance Info
109 ServiceInstance serviceInstance = decomposition.getServiceInstance()
110 def serviceInstanceId = ""
113 serviceInstanceId = execution.getVariable("serviceInstanceId")
114 logger.debug( "serviceInstanceId is: " + serviceInstanceId)
115 serviceName = execution.getVariable("subscriptionServiceType")
116 logger.debug( "serviceName is: " + serviceName)
118 if (serviceInstanceId == null || serviceInstanceId == "null") {
119 logger.debug( "Unable to obtain Service Instance Id")
120 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
121 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
123 if (serviceName == null || serviceName == "null") {
124 logger.debug( "Unable to obtain Service Name")
125 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
126 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
129 ModelInfo model = decomposition.getModelInfo()
130 logger.debug( "ModelInfo: " + model.toString())
131 String modelType = model.getModelType()
132 String modelInvariantId = model.getModelInvariantUuid()
133 String modelVersionId = model.getModelUuid()
134 String modelName = model.getModelName()
135 String modelVersion = model.getModelVersion()
137 String subscriberId = ""
138 String subscriberName = ""
139 String commonSiteId = ""
140 if (subscriber != null) {
141 subscriberId = subscriber.getGlobalId()
142 subscriberName = subscriber.getName()
143 commonSiteId = subscriber.getCommonSiteId()
146 //Determine RequestType
147 //TODO Figure out better way to determine this
148 String requestType = "create"
149 List<Resource> resources = decomposition.getServiceResources()
150 for (Resource r : resources) {
151 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
152 if (currentSolution != null) {
153 requestType = "speed changed"
158 String placementDemands = ""
159 StringBuilder sb = new StringBuilder()
160 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
161 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
163 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
164 logger.debug( "Allotted Resources List is empty - will try to get service VNFs instead.")
166 for (AllottedResource resource : allottedResourceList) {
167 logger.debug( "Allotted Resource: " + resource.toString())
168 def serviceResourceId = resource.getResourceId()
169 def toscaNodeType = resource.getToscaNodeType()
170 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
171 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
172 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
173 def resourceModelName = resource.getModelInfo().getModelName()
174 def resourceModelVersion = resource.getModelInfo().getModelVersion()
175 def resourceModelType = resource.getModelInfo().getModelType()
176 def tenantId = execution.getVariable("tenantId")
177 def requiredCandidatesJson = ""
179 requiredCandidatesJson = createCandidateJson(
186 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
187 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
188 " \"tenantId\": \"${tenantId}\",\n" +
189 " \"resourceModelInfo\": {\n" +
190 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
191 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
192 " \"modelName\": \"${resourceModelName}\",\n" +
193 " \"modelType\": \"${resourceModelType}\",\n" +
194 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
195 " \"modelCustomizationName\": \"\"\n" +
196 " }" + requiredCandidatesJson + "\n" +
199 placementDemands = sb.append(demand)
203 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
204 logger.debug( "VNF Resources List is empty")
207 for (VnfResource vnfResource : vnfResourceList) {
208 logger.debug( "VNF Resource: " + vnfResource.toString())
209 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
210 def toscaNodeType = vnfResource.getToscaNodeType()
211 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
212 def serviceResourceId = vnfResource.getResourceId()
213 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
214 def resourceModelName = vnfResourceModelInfo.getModelName()
215 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
216 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
217 def resourceModelType = vnfResourceModelInfo.getModelType()
218 def tenantId = execution.getVariable("tenantId")
219 def requiredCandidatesJson = ""
222 String placementDemand =
224 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
225 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
226 " \"tenantId\": \"${tenantId}\",\n" +
227 " \"resourceModelInfo\": {\n" +
228 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
229 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
230 " \"modelName\": \"${resourceModelName}\",\n" +
231 " \"modelType\": \"${resourceModelType}\",\n" +
232 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
233 " \"modelCustomizationName\": \"\"\n" +
234 " }" + requiredCandidatesJson + "\n" +
237 placementDemands = sb.append(placementDemand)
239 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
242 /* Commenting Out Licensing as OOF doesn't support for Beijing
243 String licenseDemands = ""
244 sb = new StringBuilder()
245 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
246 logger.debug( "Vnf Resources List is Empty")
248 for (VnfResource vnfResource : vnfResourceList) {
249 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
250 def resourceInstanceType = vnfResource.getResourceType()
251 def serviceResourceId = vnfResource.getResourceId()
252 def resourceModuleName = vnfResource.getResourceType()
253 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
254 def resouceModelName = vnfResourceModelInfo.getModelName()
255 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
256 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
257 def resouceModelType = vnfResourceModelInfo.getModelType()
259 // TODO Add Existing Licenses to demand
260 //"existingLicenses": {
261 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
262 // "43257b49-9602-4fe5-9337-094e52bc9435"],
263 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
264 // "43257b49-9602-4fe5-9337-094e52bc9435"]
267 String licenseDemand =
269 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
270 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
271 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
272 "\"resourceModelInfo\": {\n" +
273 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
274 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
275 " \"modelName\": \"${resouceModelName}\",\n" +
276 " \"modelType\": \"${resouceModelType}\",\n" +
277 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
278 " \"modelCustomizationName\": \"\"\n" +
282 licenseDemands = sb.append(licenseDemand)
284 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
289 " \"requestInfo\": {\n" +
290 " \"transactionId\": \"${transactionId}\",\n" +
291 " \"requestId\": \"${requestId}\",\n" +
292 " \"callbackUrl\": \"${callbackUrl}\",\n" +
293 " \"sourceId\": \"so\",\n" +
294 " \"requestType\": \"${requestType}\"," +
295 " \"numSolutions\": 1,\n" +
296 " \"optimizers\": [\"placement\"],\n" +
297 " \"timeout\": 600\n" +
299 " \"placementInfo\": {\n" +
300 " \"requestParameters\": {\n" +
301 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
302 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
303 " \"customerName\": \"${customerLocation.customerName}\"\n" +
305 " \"subscriberInfo\": { \n" +
306 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
307 " \"subscriberName\": \"${subscriberName}\",\n" +
308 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
310 " \"placementDemands\": [\n" +
311 " ${placementDemands}\n" +
314 " \"serviceInfo\": {\n" +
315 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
316 " \"serviceName\": \"${serviceName}\",\n" +
317 " \"modelInfo\": {\n" +
318 " \"modelType\": \"${modelType}\",\n" +
319 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
320 " \"modelVersionId\": \"${modelVersionId}\",\n" +
321 " \"modelName\": \"${modelName}\",\n" +
322 " \"modelVersion\": \"${modelVersion}\",\n" +
323 " \"modelCustomizationName\": \"\"\n" +
329 logger.debug( "Completed Building OOF Request")
331 } catch (Exception ex) {
332 logger.debug( "buildRequest Exception: " + ex)
337 * This method validates the callback response
338 * from OOF. If the response contains an
339 * exception the method will build and throw
340 * a workflow exception.
343 * @param response - the async callback response from oof
345 Void validateCallbackResponse(DelegateExecution execution, String response) {
346 String placements = ""
347 if (isBlank(response)) {
348 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
350 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
351 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
352 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
353 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
354 if (isBlank(statusMessage)) {
355 logger.debug( "Error Occurred in Homing: OOF Async Callback Response does " +
356 "not contain placement solution.")
357 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
358 "OOF Async Callback Response does not contain placement solution.")
360 logger.debug( "Error Occurred in Homing: " + statusMessage)
361 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
366 } else if (response.contains("error") || response.contains("Error") ) {
367 String errorMessage = ""
368 if (response.contains("policyException")) {
369 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
370 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
371 } else if (response.contains("Unable to find any candidate for demand")) {
372 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
373 "demand *** Response: " + response.toString()
374 } else if (response.contains("serviceException")) {
375 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
376 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
378 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
380 logger.debug( "Error Occurred in Homing: " + errorMessage)
381 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
384 logger.debug( "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.")
385 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
392 * This method creates candidates json for placement Demands.
395 * @param existingCandidates -
396 * @param excludedCandidates -
397 * @param requiredCandidates -
399 * @return candidatesJson - a JSON string with candidates
401 String createCandidateJson(ArrayList existingCandidates = null,
402 ArrayList excludedCandidates = null,
403 ArrayList requiredCandidates = null) {
404 def candidatesJson = ""
406 if (existingCandidates != null && existingCandidates != {}) {
407 sb = new StringBuilder()
409 " \"existingCandidates\": [\n")
410 def existingCandidateJson = ""
411 existingCandidates.each { existingCandidate ->
412 type = existingCandidate.get('identifierType')
413 if (type == 'vimId') {
414 def cloudOwner = existingCandidate.get('cloudOwner')
415 def cloudRegionId = existingCandidate.get('identifiers')
416 existingCandidateJson = "{\n" +
417 " \"identifierType\": \"vimId\",\n" +
418 " \"cloudOwner\": \"${cloudOwner}\",\n" +
419 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
421 sb.append(existingCandidateJson)
423 if (type == 'serviceInstanceId') {
424 def serviceInstanceId = existingCandidate.get('identifiers')
425 existingCandidateJson += "{\n" +
426 " \"identifierType\": \"serviceInstanceId\",\n" +
427 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
429 sb.append(existingCandidateJson)
432 if (existingCandidateJson != "") {
433 sb.setLength(sb.length() - 1)
434 candidatesJson = sb.append(",\n],")
437 if (excludedCandidates != null && excludedCandidates != {}) {
438 sb = new StringBuilder()
440 " \"excludedCandidates\": [\n")
441 def excludedCandidateJson = ""
442 excludedCandidates.each { excludedCandidate ->
443 type = excludedCandidate.get('identifierType')
444 if (type == 'vimId') {
445 def cloudOwner = excludedCandidate.get('cloudOwner')
446 def cloudRegionId = excludedCandidate.get('identifiers')
447 excludedCandidateJson = "{\n" +
448 " \"identifierType\": \"vimId\",\n" +
449 " \"cloudOwner\": \"${cloudOwner}\",\n" +
450 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
452 sb.append(excludedCandidateJson)
454 if (type == 'serviceInstanceId') {
455 def serviceInstanceId = excludedCandidate.get('identifiers')
456 excludedCandidateJson += "{\n" +
457 " \"identifierType\": \"serviceInstanceId\",\n" +
458 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
460 sb.append(excludedCandidateJson)
463 if (excludedCandidateJson != "") {
464 sb.setLength(sb.length() - 1)
465 candidatesJson = sb.append(",\n],")
468 if (requiredCandidates != null && requiredCandidates != {}) {
469 sb = new StringBuilder()
471 " \"requiredCandidates\": [\n")
472 def requiredCandidatesJson = ""
473 requiredCandidates.each { requiredCandidate ->
474 type = requiredCandidate.get('identifierType')
475 if (type == 'vimId') {
476 def cloudOwner = requiredCandidate.get('cloudOwner')
477 def cloudRegionId = requiredCandidate.get('identifiers')
478 requiredCandidatesJson = "{\n" +
479 " \"identifierType\": \"vimId\",\n" +
480 " \"cloudOwner\": \"${cloudOwner}\",\n" +
481 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
483 sb.append(requiredCandidatesJson)
485 if (type == 'serviceInstanceId') {
486 def serviceInstanceId = requiredCandidate.get('identifiers')
487 requiredCandidatesJson += "{\n" +
488 " \"identifierType\": \"serviceInstanceId\",\n" +
489 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
491 sb.append(requiredCandidatesJson)
494 if (requiredCandidatesJson != "") {
495 sb.setLength(sb.length() - 1)
496 candidatesJson = sb.append(",\n],")
499 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
500 return candidatesJson
504 * This method creates a cloudsite in catalog database.
506 * @param CloudSite cloudSite
510 Void createCloudSite(CloudSite cloudSite, DelegateExecution execution) {
511 oofInfraUtils.createCloudSite(cloudSite, execution)
515 * This method creates a HomingInstance in catalog database.
517 * @param HomingInstance homingInstance
521 Void createHomingInstance(HomingInstance homingInstance, DelegateExecution execution) {
522 oofInfraUtils.createHomingInstance(homingInstance, execution)
525 String getMsbHost(DelegateExecution execution) {
526 String msbHost = UrnPropertiesReader.getVariable("mso.msb.host", execution, "msb-iag.onap")
528 Integer msbPort = UrnPropertiesReader.getVariable("mso.msb.port", execution, "80").toInteger()
530 return UriBuilder.fromPath("").host(msbHost).port(msbPort).scheme("http").build().toString()