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.db.catalog.beans.CloudSite
37 import org.onap.so.rest.APIResponse
38 import org.onap.so.rest.RESTClient
39 import org.onap.so.rest.RESTConfig
40 import org.springframework.http.HttpEntity
41 import org.springframework.http.HttpHeaders
42 import org.springframework.http.HttpMethod
43 import org.springframework.http.ResponseEntity
44 import org.springframework.http.client.BufferingClientHttpRequestFactory
45 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
46 import org.springframework.web.client.RestTemplate
47 import org.springframework.web.util.UriComponentsBuilder
49 import javax.ws.rs.core.MediaType
50 import javax.ws.rs.core.Response
51 import javax.xml.ws.http.HTTPException
53 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
56 ExceptionUtil exceptionUtil = new ExceptionUtil()
57 JsonUtils jsonUtil = new JsonUtils()
59 private AbstractServiceTaskProcessor utils
61 OofUtils(AbstractServiceTaskProcessor taskProcessor) {
62 this.utils = taskProcessor
66 * This method builds the service-agnostic
67 * OOF json request to get a homing solution
68 * and license solution
72 * @param decomposition - ServiceDecomposition object
73 * @param customerLocation -
74 * @param existingCandidates -
75 * @param excludedCandidates -
76 * @param requiredCandidates -
78 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
80 String buildRequest(DelegateExecution execution,
82 ServiceDecomposition decomposition,
83 Subscriber subscriber = null,
85 ArrayList existingCandidates = null,
86 ArrayList excludedCandidates = null,
87 ArrayList requiredCandidates = null) {
88 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
89 utils.log("DEBUG", "Started Building OOF Request", isDebugEnabled)
90 String callbackEndpoint = UrnPropertiesReader.getVariable("mso.oof.callbackEndpoint", execution)
91 utils.log("DEBUG", "mso.oof.callbackEndpoint is: " + callbackEndpoint, isDebugEnabled)
93 def callbackUrl = utils.createHomingCallbackURL(callbackEndpoint, "oofResponse", requestId)
94 utils.log("DEBUG", "callbackUrl is: " + callbackUrl, isDebugEnabled)
97 def transactionId = requestId
98 utils.log("DEBUG", "transactionId is: " + transactionId, isDebugEnabled)
99 //ServiceInstance Info
100 ServiceInstance serviceInstance = decomposition.getServiceInstance()
101 def serviceInstanceId = ""
104 serviceInstanceId = execution.getVariable("serviceInstanceId")
105 utils.log("DEBUG", "serviceInstanceId is: " + serviceInstanceId, isDebugEnabled)
106 serviceName = execution.getVariable("subscriptionServiceType")
107 utils.log("DEBUG", "serviceName is: " + serviceName, isDebugEnabled)
109 if (serviceInstanceId == null || serviceInstanceId == "null") {
110 utils.log("DEBUG", "Unable to obtain Service Instance Id", isDebugEnabled)
111 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
112 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
114 if (serviceName == null || serviceName == "null") {
115 utils.log("DEBUG", "Unable to obtain Service Name", isDebugEnabled)
116 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
117 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
120 ModelInfo model = decomposition.getModelInfo()
121 utils.log("DEBUG", "ModelInfo: " + model.toString(), isDebugEnabled)
122 String modelType = model.getModelType()
123 String modelInvariantId = model.getModelInvariantUuid()
124 String modelVersionId = model.getModelUuid()
125 String modelName = model.getModelName()
126 String modelVersion = model.getModelVersion()
128 String subscriberId = ""
129 String subscriberName = ""
130 String commonSiteId = ""
131 if (subscriber != null) {
132 subscriberId = subscriber.getGlobalId()
133 subscriberName = subscriber.getName()
134 commonSiteId = subscriber.getCommonSiteId()
137 //Determine RequestType
138 //TODO Figure out better way to determine this
139 String requestType = "create"
140 List<Resource> resources = decomposition.getServiceResources()
141 for (Resource r : resources) {
142 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
143 if (currentSolution != null) {
144 requestType = "speed changed"
149 String placementDemands = ""
150 StringBuilder sb = new StringBuilder()
151 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
152 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
154 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
155 utils.log("DEBUG", "Allotted Resources List is empty - will try to get service VNFs instead.",
158 for (AllottedResource resource : allottedResourceList) {
159 utils.log("DEBUG", "Allotted Resource: " + resource.toString(),
161 def serviceResourceId = resource.getResourceId()
162 def toscaNodeType = resource.getToscaNodeType()
163 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
164 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
165 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
166 def resourceModelName = resource.getModelInfo().getModelName()
167 def resourceModelVersion = resource.getModelInfo().getModelVersion()
168 def resourceModelType = resource.getModelInfo().getModelType()
169 def tenantId = execution.getVariable("tenantId")
170 def requiredCandidatesJson = ""
172 requiredCandidatesJson = createCandidateJson(
179 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
180 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
181 " \"tenantId\": \"${tenantId}\",\n" +
182 " \"resourceModelInfo\": {\n" +
183 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
184 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
185 " \"modelName\": \"${resourceModelName}\",\n" +
186 " \"modelType\": \"${resourceModelType}\",\n" +
187 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
188 " \"modelCustomizationName\": \"\"\n" +
189 " }" + requiredCandidatesJson + "\n" +
192 placementDemands = sb.append(demand)
196 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
197 utils.log("DEBUG", "VNF Resources List is empty",
201 for (VnfResource vnfResource : vnfResourceList) {
202 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
204 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
205 def toscaNodeType = vnfResource.getToscaNodeType()
206 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
207 def serviceResourceId = vnfResource.getResourceId()
208 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
209 def resourceModelName = vnfResourceModelInfo.getModelName()
210 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
211 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
212 def resourceModelType = vnfResourceModelInfo.getModelType()
213 def tenantId = execution.getVariable("tenantId")
214 def requiredCandidatesJson = ""
217 String placementDemand =
219 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
220 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
221 " \"tenantId\": \"${tenantId}\",\n" +
222 " \"resourceModelInfo\": {\n" +
223 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
224 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
225 " \"modelName\": \"${resourceModelName}\",\n" +
226 " \"modelType\": \"${resourceModelType}\",\n" +
227 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
228 " \"modelCustomizationName\": \"\"\n" +
229 " }" + requiredCandidatesJson + "\n" +
232 placementDemands = sb.append(placementDemand)
234 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
237 /* Commenting Out Licensing as OOF doesn't support for Beijing
238 String licenseDemands = ""
239 sb = new StringBuilder()
240 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
241 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
243 for (VnfResource vnfResource : vnfResourceList) {
244 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
245 def resourceInstanceType = vnfResource.getResourceType()
246 def serviceResourceId = vnfResource.getResourceId()
247 def resourceModuleName = vnfResource.getResourceType()
248 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
249 def resouceModelName = vnfResourceModelInfo.getModelName()
250 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
251 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
252 def resouceModelType = vnfResourceModelInfo.getModelType()
254 // TODO Add Existing Licenses to demand
255 //"existingLicenses": {
256 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
257 // "43257b49-9602-4fe5-9337-094e52bc9435"],
258 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
259 // "43257b49-9602-4fe5-9337-094e52bc9435"]
262 String licenseDemand =
264 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
265 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
266 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
267 "\"resourceModelInfo\": {\n" +
268 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
269 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
270 " \"modelName\": \"${resouceModelName}\",\n" +
271 " \"modelType\": \"${resouceModelType}\",\n" +
272 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
273 " \"modelCustomizationName\": \"\"\n" +
277 licenseDemands = sb.append(licenseDemand)
279 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
284 " \"requestInfo\": {\n" +
285 " \"transactionId\": \"${transactionId}\",\n" +
286 " \"requestId\": \"${requestId}\",\n" +
287 " \"callbackUrl\": \"${callbackUrl}\",\n" +
288 " \"sourceId\": \"so\",\n" +
289 " \"requestType\": \"${requestType}\"," +
290 " \"numSolutions\": 1,\n" +
291 " \"optimizers\": [\"placement\"],\n" +
292 " \"timeout\": 600\n" +
294 " \"placementInfo\": {\n" +
295 " \"requestParameters\": {\n" +
296 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
297 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
298 " \"customerName\": \"${customerLocation.customerName}\"\n" +
300 " \"subscriberInfo\": { \n" +
301 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
302 " \"subscriberName\": \"${subscriberName}\",\n" +
303 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
305 " \"placementDemands\": [\n" +
306 " ${placementDemands}\n" +
309 " \"serviceInfo\": {\n" +
310 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
311 " \"serviceName\": \"${serviceName}\",\n" +
312 " \"modelInfo\": {\n" +
313 " \"modelType\": \"${modelType}\",\n" +
314 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
315 " \"modelVersionId\": \"${modelVersionId}\",\n" +
316 " \"modelName\": \"${modelName}\",\n" +
317 " \"modelVersion\": \"${modelVersion}\",\n" +
318 " \"modelCustomizationName\": \"\"\n" +
324 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
326 } catch (Exception ex) {
327 utils.log("DEBUG", "buildRequest Exception: " + ex, isDebugEnabled)
332 * This method validates the callback response
333 * from OOF. If the response contains an
334 * exception the method will build and throw
335 * a workflow exception.
338 * @param response - the async callback response from oof
340 Void validateCallbackResponse(DelegateExecution execution, String response) {
341 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
342 String placements = ""
343 if (isBlank(response)) {
344 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
346 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
347 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
348 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
349 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
350 if (isBlank(statusMessage)) {
351 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
352 "not contain placement solution.", isDebugEnabled)
353 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
354 "OOF Async Callback Response does not contain placement solution.")
356 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
357 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
362 } else if (response.contains("error") || response.contains("Error") ) {
363 String errorMessage = ""
364 if (response.contains("policyException")) {
365 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
366 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
367 } else if (response.contains("Unable to find any candidate for demand")) {
368 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
369 "demand *** Response: " + response.toString()
370 } else if (response.contains("serviceException")) {
371 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
372 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
374 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
376 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
377 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
380 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
381 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
388 * This method creates candidates json for placement Demands.
391 * @param existingCandidates -
392 * @param excludedCandidates -
393 * @param requiredCandidates -
395 * @return candidatesJson - a JSON string with candidates
397 String createCandidateJson(ArrayList existingCandidates = null,
398 ArrayList excludedCandidates = null,
399 ArrayList requiredCandidates = null) {
400 def candidatesJson = ""
402 if (existingCandidates != null && existingCandidates != {}) {
403 sb = new StringBuilder()
405 " \"existingCandidates\": [\n")
406 def existingCandidateJson = ""
407 existingCandidates.each { existingCandidate ->
408 type = existingCandidate.get('identifierType')
409 if (type == 'vimId') {
410 def cloudOwner = existingCandidate.get('cloudOwner')
411 def cloudRegionId = existingCandidate.get('identifiers')
412 existingCandidateJson = "{\n" +
413 " \"identifierType\": \"vimId\",\n" +
414 " \"cloudOwner\": \"${cloudOwner}\",\n" +
415 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
417 sb.append(existingCandidateJson)
419 if (type == 'serviceInstanceId') {
420 def serviceInstanceId = existingCandidate.get('identifiers')
421 existingCandidateJson += "{\n" +
422 " \"identifierType\": \"serviceInstanceId\",\n" +
423 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
425 sb.append(existingCandidateJson)
428 if (existingCandidateJson != "") {
429 sb.setLength(sb.length() - 1)
430 candidatesJson = sb.append(",\n],")
433 if (excludedCandidates != null && excludedCandidates != {}) {
434 sb = new StringBuilder()
436 " \"excludedCandidates\": [\n")
437 def excludedCandidateJson = ""
438 excludedCandidates.each { excludedCandidate ->
439 type = excludedCandidate.get('identifierType')
440 if (type == 'vimId') {
441 def cloudOwner = excludedCandidate.get('cloudOwner')
442 def cloudRegionId = excludedCandidate.get('identifiers')
443 excludedCandidateJson = "{\n" +
444 " \"identifierType\": \"vimId\",\n" +
445 " \"cloudOwner\": \"${cloudOwner}\",\n" +
446 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
448 sb.append(excludedCandidateJson)
450 if (type == 'serviceInstanceId') {
451 def serviceInstanceId = excludedCandidate.get('identifiers')
452 excludedCandidateJson += "{\n" +
453 " \"identifierType\": \"serviceInstanceId\",\n" +
454 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
456 sb.append(excludedCandidateJson)
459 if (excludedCandidateJson != "") {
460 sb.setLength(sb.length() - 1)
461 candidatesJson = sb.append(",\n],")
464 if (requiredCandidates != null && requiredCandidates != {}) {
465 sb = new StringBuilder()
467 " \"requiredCandidates\": [\n")
468 def requiredCandidatesJson = ""
469 requiredCandidates.each { requiredCandidate ->
470 type = requiredCandidate.get('identifierType')
471 if (type == 'vimId') {
472 def cloudOwner = requiredCandidate.get('cloudOwner')
473 def cloudRegionId = requiredCandidate.get('identifiers')
474 requiredCandidatesJson = "{\n" +
475 " \"identifierType\": \"vimId\",\n" +
476 " \"cloudOwner\": \"${cloudOwner}\",\n" +
477 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
479 sb.append(requiredCandidatesJson)
481 if (type == 'serviceInstanceId') {
482 def serviceInstanceId = requiredCandidate.get('identifiers')
483 requiredCandidatesJson += "{\n" +
484 " \"identifierType\": \"serviceInstanceId\",\n" +
485 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
487 sb.append(requiredCandidatesJson)
490 if (requiredCandidatesJson != "") {
491 sb.setLength(sb.length() - 1)
492 candidatesJson = sb.append(",\n],")
495 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
496 return candidatesJson
499 * This method creates a cloudsite in catalog database.
501 * @param CloudSite cloudSite
505 Void createCloudSiteCatalogDb(CloudSite cloudSite, DelegateExecution execution) {
507 String endpoint = UrnPropertiesReader.getVariable("mso.catalog.db.spring.endpoint", execution)
508 String auth = UrnPropertiesReader.getVariable("mso.db.auth", execution)
509 String uri = "/cloudSite"
511 HttpHeaders headers = new HttpHeaders()
513 headers.set(HttpHeaders.AUTHORIZATION, auth)
514 headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
515 headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
517 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(endpoint + uri)
518 HttpEntity<CloudSite> request = new HttpEntity<CloudSite>(cloudSite, headers)
519 RESTConfig config = new RESTConfig(endpoint + uri)
520 RESTClient client = new RESTClient(config).addAuthorizationHeader(auth).
521 addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON).addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
522 APIResponse response = client.httpPost(request.getBody().toString())
524 int responseCode = response.getStatusCode()
525 logDebug("CatalogDB response code is: " + responseCode, isDebugEnabled)
526 String syncResponse = response.getResponseBodyAsString()
527 logDebug("CatalogDB response is: " + syncResponse, isDebugEnabled)
529 if(responseCode != 202){
530 exceptionUtil.buildAndThrowWorkflowException(execution, responseCode, "Received a Bad Sync Response from CatalogDB.")
534 String getMsbHost(DelegateExecution execution) {
535 msbHost = UrnPropertiesReader.getVariable("mso.msb.host", execution, "msb-iag.onap")
537 Integer msbPort = UrnPropertiesReader.getVariable("mso.msb.port", execution, "80").toInteger()
539 return UriBuilder.fromPath("").host(msbHost).port(msbPort).scheme("http").build().toString()