2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 Intel Corp. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.onap.so.bpmn.common.scripts
23 import org.camunda.bpm.engine.delegate.DelegateExecution
24 import org.onap.so.bpmn.common.scripts.AbstractServiceTaskProcessor
25 import org.onap.so.bpmn.common.scripts.ExceptionUtil
26 import org.onap.so.bpmn.core.UrnPropertiesReader
27 import org.onap.so.bpmn.core.domain.HomingSolution
28 import org.onap.so.bpmn.core.domain.ModelInfo
29 import org.onap.so.bpmn.core.domain.Resource
30 import org.onap.so.bpmn.core.domain.AllottedResource
31 import org.onap.so.bpmn.core.domain.ServiceDecomposition
32 import org.onap.so.bpmn.core.domain.ServiceInstance
33 import org.onap.so.bpmn.core.domain.Subscriber
34 import org.onap.so.bpmn.core.domain.VnfResource
35 import org.onap.so.bpmn.core.json.JsonUtils
36 import org.onap.so.client.HttpClient
37 import org.onap.so.db.catalog.beans.CloudSite
38 import org.onap.so.utils.TargetEntity
39 import org.springframework.http.HttpEntity
40 import org.springframework.http.HttpHeaders
41 import org.springframework.http.HttpMethod
42 import org.springframework.http.ResponseEntity
43 import org.springframework.http.client.BufferingClientHttpRequestFactory
44 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
45 import org.springframework.web.client.RestTemplate
46 import org.springframework.web.util.UriComponentsBuilder
48 import javax.ws.rs.core.MediaType
49 import javax.ws.rs.core.Response
50 import javax.xml.ws.http.HTTPException
52 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
55 ExceptionUtil exceptionUtil = new ExceptionUtil()
56 JsonUtils jsonUtil = new JsonUtils()
58 private AbstractServiceTaskProcessor utils
60 OofUtils(AbstractServiceTaskProcessor taskProcessor) {
61 this.utils = taskProcessor
65 * This method builds the service-agnostic
66 * OOF json request to get a homing solution
67 * and license solution
71 * @param decomposition - ServiceDecomposition object
72 * @param customerLocation -
73 * @param existingCandidates -
74 * @param excludedCandidates -
75 * @param requiredCandidates -
77 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
79 String buildRequest(DelegateExecution execution,
81 ServiceDecomposition decomposition,
82 Subscriber subscriber = null,
84 ArrayList existingCandidates = null,
85 ArrayList excludedCandidates = null,
86 ArrayList requiredCandidates = null) {
87 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
88 utils.log("DEBUG", "Started Building OOF Request", isDebugEnabled)
89 String callbackEndpoint = UrnPropertiesReader.getVariable("mso.oof.callbackEndpoint", execution)
90 utils.log("DEBUG", "mso.oof.callbackEndpoint is: " + callbackEndpoint, isDebugEnabled)
92 def callbackUrl = utils.createHomingCallbackURL(callbackEndpoint, "oofResponse", requestId)
93 utils.log("DEBUG", "callbackUrl is: " + callbackUrl, isDebugEnabled)
96 def transactionId = requestId
97 utils.log("DEBUG", "transactionId is: " + transactionId, isDebugEnabled)
98 //ServiceInstance Info
99 ServiceInstance serviceInstance = decomposition.getServiceInstance()
100 def serviceInstanceId = ""
103 serviceInstanceId = execution.getVariable("serviceInstanceId")
104 utils.log("DEBUG", "serviceInstanceId is: " + serviceInstanceId, isDebugEnabled)
105 serviceName = execution.getVariable("subscriptionServiceType")
106 utils.log("DEBUG", "serviceName is: " + serviceName, isDebugEnabled)
108 if (serviceInstanceId == null || serviceInstanceId == "null") {
109 utils.log("DEBUG", "Unable to obtain Service Instance Id", isDebugEnabled)
110 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
111 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
113 if (serviceName == null || serviceName == "null") {
114 utils.log("DEBUG", "Unable to obtain Service Name", isDebugEnabled)
115 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
116 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
119 ModelInfo model = decomposition.getModelInfo()
120 utils.log("DEBUG", "ModelInfo: " + model.toString(), isDebugEnabled)
121 String modelType = model.getModelType()
122 String modelInvariantId = model.getModelInvariantUuid()
123 String modelVersionId = model.getModelUuid()
124 String modelName = model.getModelName()
125 String modelVersion = model.getModelVersion()
127 String subscriberId = ""
128 String subscriberName = ""
129 String commonSiteId = ""
130 if (subscriber != null) {
131 subscriberId = subscriber.getGlobalId()
132 subscriberName = subscriber.getName()
133 commonSiteId = subscriber.getCommonSiteId()
136 //Determine RequestType
137 //TODO Figure out better way to determine this
138 String requestType = "create"
139 List<Resource> resources = decomposition.getServiceResources()
140 for (Resource r : resources) {
141 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
142 if (currentSolution != null) {
143 requestType = "speed changed"
148 String placementDemands = ""
149 StringBuilder sb = new StringBuilder()
150 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
151 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
153 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
154 utils.log("DEBUG", "Allotted Resources List is empty - will try to get service VNFs instead.",
157 for (AllottedResource resource : allottedResourceList) {
158 utils.log("DEBUG", "Allotted Resource: " + resource.toString(),
160 def serviceResourceId = resource.getResourceId()
161 def toscaNodeType = resource.getToscaNodeType()
162 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
163 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
164 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
165 def resourceModelName = resource.getModelInfo().getModelName()
166 def resourceModelVersion = resource.getModelInfo().getModelVersion()
167 def resourceModelType = resource.getModelInfo().getModelType()
168 def tenantId = execution.getVariable("tenantId")
169 def requiredCandidatesJson = ""
171 requiredCandidatesJson = createCandidateJson(
178 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
179 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
180 " \"tenantId\": \"${tenantId}\",\n" +
181 " \"resourceModelInfo\": {\n" +
182 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
183 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
184 " \"modelName\": \"${resourceModelName}\",\n" +
185 " \"modelType\": \"${resourceModelType}\",\n" +
186 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
187 " \"modelCustomizationName\": \"\"\n" +
188 " }" + requiredCandidatesJson + "\n" +
191 placementDemands = sb.append(demand)
195 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
196 utils.log("DEBUG", "VNF Resources List is empty",
200 for (VnfResource vnfResource : vnfResourceList) {
201 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
203 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
204 def toscaNodeType = vnfResource.getToscaNodeType()
205 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
206 def serviceResourceId = vnfResource.getResourceId()
207 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
208 def resourceModelName = vnfResourceModelInfo.getModelName()
209 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
210 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
211 def resourceModelType = vnfResourceModelInfo.getModelType()
212 def tenantId = execution.getVariable("tenantId")
213 def requiredCandidatesJson = ""
216 String placementDemand =
218 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
219 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
220 " \"tenantId\": \"${tenantId}\",\n" +
221 " \"resourceModelInfo\": {\n" +
222 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
223 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
224 " \"modelName\": \"${resourceModelName}\",\n" +
225 " \"modelType\": \"${resourceModelType}\",\n" +
226 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
227 " \"modelCustomizationName\": \"\"\n" +
228 " }" + requiredCandidatesJson + "\n" +
231 placementDemands = sb.append(placementDemand)
233 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
236 /* Commenting Out Licensing as OOF doesn't support for Beijing
237 String licenseDemands = ""
238 sb = new StringBuilder()
239 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
240 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
242 for (VnfResource vnfResource : vnfResourceList) {
243 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
244 def resourceInstanceType = vnfResource.getResourceType()
245 def serviceResourceId = vnfResource.getResourceId()
246 def resourceModuleName = vnfResource.getResourceType()
247 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
248 def resouceModelName = vnfResourceModelInfo.getModelName()
249 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
250 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
251 def resouceModelType = vnfResourceModelInfo.getModelType()
253 // TODO Add Existing Licenses to demand
254 //"existingLicenses": {
255 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
256 // "43257b49-9602-4fe5-9337-094e52bc9435"],
257 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
258 // "43257b49-9602-4fe5-9337-094e52bc9435"]
261 String licenseDemand =
263 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
264 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
265 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
266 "\"resourceModelInfo\": {\n" +
267 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
268 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
269 " \"modelName\": \"${resouceModelName}\",\n" +
270 " \"modelType\": \"${resouceModelType}\",\n" +
271 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
272 " \"modelCustomizationName\": \"\"\n" +
276 licenseDemands = sb.append(licenseDemand)
278 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
283 " \"requestInfo\": {\n" +
284 " \"transactionId\": \"${transactionId}\",\n" +
285 " \"requestId\": \"${requestId}\",\n" +
286 " \"callbackUrl\": \"${callbackUrl}\",\n" +
287 " \"sourceId\": \"so\",\n" +
288 " \"requestType\": \"${requestType}\"," +
289 " \"numSolutions\": 1,\n" +
290 " \"optimizers\": [\"placement\"],\n" +
291 " \"timeout\": 600\n" +
293 " \"placementInfo\": {\n" +
294 " \"requestParameters\": {\n" +
295 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
296 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
297 " \"customerName\": \"${customerLocation.customerName}\"\n" +
299 " \"subscriberInfo\": { \n" +
300 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
301 " \"subscriberName\": \"${subscriberName}\",\n" +
302 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
304 " \"placementDemands\": [\n" +
305 " ${placementDemands}\n" +
308 " \"serviceInfo\": {\n" +
309 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
310 " \"serviceName\": \"${serviceName}\",\n" +
311 " \"modelInfo\": {\n" +
312 " \"modelType\": \"${modelType}\",\n" +
313 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
314 " \"modelVersionId\": \"${modelVersionId}\",\n" +
315 " \"modelName\": \"${modelName}\",\n" +
316 " \"modelVersion\": \"${modelVersion}\",\n" +
317 " \"modelCustomizationName\": \"\"\n" +
323 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
325 } catch (Exception ex) {
326 utils.log("DEBUG", "buildRequest Exception: " + ex, isDebugEnabled)
331 * This method validates the callback response
332 * from OOF. If the response contains an
333 * exception the method will build and throw
334 * a workflow exception.
337 * @param response - the async callback response from oof
339 Void validateCallbackResponse(DelegateExecution execution, String response) {
340 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
341 String placements = ""
342 if (isBlank(response)) {
343 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
345 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
346 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
347 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
348 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
349 if (isBlank(statusMessage)) {
350 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
351 "not contain placement solution.", isDebugEnabled)
352 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
353 "OOF Async Callback Response does not contain placement solution.")
355 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
356 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
361 } else if (response.contains("error") || response.contains("Error") ) {
362 String errorMessage = ""
363 if (response.contains("policyException")) {
364 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
365 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
366 } else if (response.contains("Unable to find any candidate for demand")) {
367 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
368 "demand *** Response: " + response.toString()
369 } else if (response.contains("serviceException")) {
370 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
371 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
373 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
375 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
376 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
379 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
380 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
387 * This method creates candidates json for placement Demands.
390 * @param existingCandidates -
391 * @param excludedCandidates -
392 * @param requiredCandidates -
394 * @return candidatesJson - a JSON string with candidates
396 String createCandidateJson(ArrayList existingCandidates = null,
397 ArrayList excludedCandidates = null,
398 ArrayList requiredCandidates = null) {
399 def candidatesJson = ""
401 if (existingCandidates != null && existingCandidates != {}) {
402 sb = new StringBuilder()
404 " \"existingCandidates\": [\n")
405 def existingCandidateJson = ""
406 existingCandidates.each { existingCandidate ->
407 type = existingCandidate.get('identifierType')
408 if (type == 'vimId') {
409 def cloudOwner = existingCandidate.get('cloudOwner')
410 def cloudRegionId = existingCandidate.get('identifiers')
411 existingCandidateJson = "{\n" +
412 " \"identifierType\": \"vimId\",\n" +
413 " \"cloudOwner\": \"${cloudOwner}\",\n" +
414 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
416 sb.append(existingCandidateJson)
418 if (type == 'serviceInstanceId') {
419 def serviceInstanceId = existingCandidate.get('identifiers')
420 existingCandidateJson += "{\n" +
421 " \"identifierType\": \"serviceInstanceId\",\n" +
422 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
424 sb.append(existingCandidateJson)
427 if (existingCandidateJson != "") {
428 sb.setLength(sb.length() - 1)
429 candidatesJson = sb.append(",\n],")
432 if (excludedCandidates != null && excludedCandidates != {}) {
433 sb = new StringBuilder()
435 " \"excludedCandidates\": [\n")
436 def excludedCandidateJson = ""
437 excludedCandidates.each { excludedCandidate ->
438 type = excludedCandidate.get('identifierType')
439 if (type == 'vimId') {
440 def cloudOwner = excludedCandidate.get('cloudOwner')
441 def cloudRegionId = excludedCandidate.get('identifiers')
442 excludedCandidateJson = "{\n" +
443 " \"identifierType\": \"vimId\",\n" +
444 " \"cloudOwner\": \"${cloudOwner}\",\n" +
445 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
447 sb.append(excludedCandidateJson)
449 if (type == 'serviceInstanceId') {
450 def serviceInstanceId = excludedCandidate.get('identifiers')
451 excludedCandidateJson += "{\n" +
452 " \"identifierType\": \"serviceInstanceId\",\n" +
453 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
455 sb.append(excludedCandidateJson)
458 if (excludedCandidateJson != "") {
459 sb.setLength(sb.length() - 1)
460 candidatesJson = sb.append(",\n],")
463 if (requiredCandidates != null && requiredCandidates != {}) {
464 sb = new StringBuilder()
466 " \"requiredCandidates\": [\n")
467 def requiredCandidatesJson = ""
468 requiredCandidates.each { requiredCandidate ->
469 type = requiredCandidate.get('identifierType')
470 if (type == 'vimId') {
471 def cloudOwner = requiredCandidate.get('cloudOwner')
472 def cloudRegionId = requiredCandidate.get('identifiers')
473 requiredCandidatesJson = "{\n" +
474 " \"identifierType\": \"vimId\",\n" +
475 " \"cloudOwner\": \"${cloudOwner}\",\n" +
476 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
478 sb.append(requiredCandidatesJson)
480 if (type == 'serviceInstanceId') {
481 def serviceInstanceId = requiredCandidate.get('identifiers')
482 requiredCandidatesJson += "{\n" +
483 " \"identifierType\": \"serviceInstanceId\",\n" +
484 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
486 sb.append(requiredCandidatesJson)
489 if (requiredCandidatesJson != "") {
490 sb.setLength(sb.length() - 1)
491 candidatesJson = sb.append(",\n],")
494 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
495 return candidatesJson
498 * This method creates a cloudsite in catalog database.
500 * @param CloudSite cloudSite
504 Void createCloudSiteCatalogDb(CloudSite cloudSite, DelegateExecution execution) {
506 String endpoint = UrnPropertiesReader.getVariable("mso.catalog.db.spring.endpoint", execution)
507 String auth = UrnPropertiesReader.getVariable("mso.db.auth", execution)
508 String uri = "/cloudSite"
510 URL url = new URL(endpoint + uri)
511 HttpClient client = new HttpClient(url, MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)
512 client.addAdditionalHeader(HttpHeaders.AUTHORIZATION, auth)
513 client.addAdditionalHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
515 Response response = client.post(request.getBody().toString())
517 int responseCode = response.getStatus()
518 logDebug("CatalogDB response code is: " + responseCode, isDebugEnabled)
519 String syncResponse = response.readEntity(String.class)
520 logDebug("CatalogDB response is: " + syncResponse, isDebugEnabled)
522 if(responseCode != 202){
523 exceptionUtil.buildAndThrowWorkflowException(execution, responseCode, "Received a Bad Sync Response from CatalogDB.")
527 String getMsbHost(DelegateExecution execution) {
528 msbHost = UrnPropertiesReader.getVariable("mso.msb.host", execution, "msb-iag.onap")
530 Integer msbPort = UrnPropertiesReader.getVariable("mso.msb.port", execution, "80").toInteger()
532 return UriBuilder.fromPath("").host(msbHost).port(msbPort).scheme("http").build().toString()