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.core.UrnPropertiesReader
29 import org.onap.so.bpmn.core.domain.HomingSolution
30 import org.onap.so.bpmn.core.domain.ModelInfo
31 import org.onap.so.bpmn.core.domain.Resource
32 import org.onap.so.bpmn.core.domain.AllottedResource
33 import org.onap.so.bpmn.core.domain.ServiceDecomposition
34 import org.onap.so.bpmn.core.domain.ServiceInstance
35 import org.onap.so.bpmn.core.domain.Subscriber
36 import org.onap.so.bpmn.core.domain.VnfResource
37 import org.onap.so.bpmn.core.json.JsonUtils
38 import org.onap.so.client.HttpClient
39 import org.onap.so.client.HttpClientFactory
40 import org.onap.so.db.catalog.beans.CloudSite
41 import org.onap.so.db.catalog.beans.HomingInstance
42 import org.onap.so.utils.TargetEntity
43 import org.springframework.http.HttpEntity
44 import org.springframework.http.HttpHeaders
45 import org.springframework.http.HttpMethod
46 import org.springframework.http.ResponseEntity
47 import org.springframework.http.client.BufferingClientHttpRequestFactory
48 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
49 import org.springframework.web.client.RestTemplate
50 import org.springframework.web.util.UriComponentsBuilder
51 import org.slf4j.Logger
52 import org.slf4j.LoggerFactory
54 import javax.ws.rs.core.MediaType
55 import javax.ws.rs.core.Response
56 import javax.xml.ws.http.HTTPException
58 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
61 private static final Logger logger = LoggerFactory.getLogger( OofUtils.class);
63 ExceptionUtil exceptionUtil = new ExceptionUtil()
64 JsonUtils jsonUtil = new JsonUtils()
66 private AbstractServiceTaskProcessor utils
68 OofUtils(AbstractServiceTaskProcessor taskProcessor) {
69 this.utils = taskProcessor
73 * This method builds the service-agnostic
74 * OOF json request to get a homing solution
75 * and license solution
79 * @param decomposition - ServiceDecomposition object
80 * @param customerLocation -
81 * @param existingCandidates -
82 * @param excludedCandidates -
83 * @param requiredCandidates -
85 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
87 String buildRequest(DelegateExecution execution,
89 ServiceDecomposition decomposition,
90 Subscriber subscriber = null,
92 ArrayList existingCandidates = null,
93 ArrayList excludedCandidates = null,
94 ArrayList requiredCandidates = null) {
95 logger.debug( "Started Building OOF Request")
96 String callbackEndpoint = UrnPropertiesReader.getVariable("mso.oof.callbackEndpoint", execution)
97 logger.debug( "mso.oof.callbackEndpoint is: " + callbackEndpoint)
99 def callbackUrl = utils.createHomingCallbackURL(callbackEndpoint, "oofResponse", requestId)
100 logger.debug( "callbackUrl is: " + callbackUrl)
103 def transactionId = requestId
104 logger.debug( "transactionId is: " + transactionId)
105 //ServiceInstance Info
106 ServiceInstance serviceInstance = decomposition.getServiceInstance()
107 def serviceInstanceId = ""
110 serviceInstanceId = execution.getVariable("serviceInstanceId")
111 logger.debug( "serviceInstanceId is: " + serviceInstanceId)
112 serviceName = execution.getVariable("subscriptionServiceType")
113 logger.debug( "serviceName is: " + serviceName)
115 if (serviceInstanceId == null || serviceInstanceId == "null") {
116 logger.debug( "Unable to obtain Service Instance Id")
117 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
118 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
120 if (serviceName == null || serviceName == "null") {
121 logger.debug( "Unable to obtain Service Name")
122 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
123 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
126 ModelInfo model = decomposition.getModelInfo()
127 logger.debug( "ModelInfo: " + model.toString())
128 String modelType = model.getModelType()
129 String modelInvariantId = model.getModelInvariantUuid()
130 String modelVersionId = model.getModelUuid()
131 String modelName = model.getModelName()
132 String modelVersion = model.getModelVersion()
134 String subscriberId = ""
135 String subscriberName = ""
136 String commonSiteId = ""
137 if (subscriber != null) {
138 subscriberId = subscriber.getGlobalId()
139 subscriberName = subscriber.getName()
140 commonSiteId = subscriber.getCommonSiteId()
143 //Determine RequestType
144 //TODO Figure out better way to determine this
145 String requestType = "create"
146 List<Resource> resources = decomposition.getServiceResources()
147 for (Resource r : resources) {
148 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
149 if (currentSolution != null) {
150 requestType = "speed changed"
155 String placementDemands = ""
156 StringBuilder sb = new StringBuilder()
157 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
158 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
160 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
161 logger.debug( "Allotted Resources List is empty - will try to get service VNFs instead.")
163 for (AllottedResource resource : allottedResourceList) {
164 logger.debug( "Allotted Resource: " + resource.toString())
165 def serviceResourceId = resource.getResourceId()
166 def toscaNodeType = resource.getToscaNodeType()
167 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
168 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
169 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
170 def resourceModelName = resource.getModelInfo().getModelName()
171 def resourceModelVersion = resource.getModelInfo().getModelVersion()
172 def resourceModelType = resource.getModelInfo().getModelType()
173 def tenantId = execution.getVariable("tenantId")
174 def requiredCandidatesJson = ""
176 requiredCandidatesJson = createCandidateJson(
183 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
184 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
185 " \"tenantId\": \"${tenantId}\",\n" +
186 " \"resourceModelInfo\": {\n" +
187 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
188 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
189 " \"modelName\": \"${resourceModelName}\",\n" +
190 " \"modelType\": \"${resourceModelType}\",\n" +
191 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
192 " \"modelCustomizationName\": \"\"\n" +
193 " }" + requiredCandidatesJson + "\n" +
196 placementDemands = sb.append(demand)
200 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
201 logger.debug( "VNF Resources List is empty")
204 for (VnfResource vnfResource : vnfResourceList) {
205 logger.debug( "VNF Resource: " + vnfResource.toString())
206 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
207 def toscaNodeType = vnfResource.getToscaNodeType()
208 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
209 def serviceResourceId = vnfResource.getResourceId()
210 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
211 def resourceModelName = vnfResourceModelInfo.getModelName()
212 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
213 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
214 def resourceModelType = vnfResourceModelInfo.getModelType()
215 def tenantId = execution.getVariable("tenantId")
216 def requiredCandidatesJson = ""
219 String placementDemand =
221 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
222 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
223 " \"tenantId\": \"${tenantId}\",\n" +
224 " \"resourceModelInfo\": {\n" +
225 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
226 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
227 " \"modelName\": \"${resourceModelName}\",\n" +
228 " \"modelType\": \"${resourceModelType}\",\n" +
229 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
230 " \"modelCustomizationName\": \"\"\n" +
231 " }" + requiredCandidatesJson + "\n" +
234 placementDemands = sb.append(placementDemand)
236 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
239 /* Commenting Out Licensing as OOF doesn't support for Beijing
240 String licenseDemands = ""
241 sb = new StringBuilder()
242 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
243 logger.debug( "Vnf Resources List is Empty")
245 for (VnfResource vnfResource : vnfResourceList) {
246 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
247 def resourceInstanceType = vnfResource.getResourceType()
248 def serviceResourceId = vnfResource.getResourceId()
249 def resourceModuleName = vnfResource.getResourceType()
250 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
251 def resouceModelName = vnfResourceModelInfo.getModelName()
252 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
253 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
254 def resouceModelType = vnfResourceModelInfo.getModelType()
256 // TODO Add Existing Licenses to demand
257 //"existingLicenses": {
258 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
259 // "43257b49-9602-4fe5-9337-094e52bc9435"],
260 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
261 // "43257b49-9602-4fe5-9337-094e52bc9435"]
264 String licenseDemand =
266 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
267 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
268 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
269 "\"resourceModelInfo\": {\n" +
270 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
271 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
272 " \"modelName\": \"${resouceModelName}\",\n" +
273 " \"modelType\": \"${resouceModelType}\",\n" +
274 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
275 " \"modelCustomizationName\": \"\"\n" +
279 licenseDemands = sb.append(licenseDemand)
281 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
286 " \"requestInfo\": {\n" +
287 " \"transactionId\": \"${transactionId}\",\n" +
288 " \"requestId\": \"${requestId}\",\n" +
289 " \"callbackUrl\": \"${callbackUrl}\",\n" +
290 " \"sourceId\": \"so\",\n" +
291 " \"requestType\": \"${requestType}\"," +
292 " \"numSolutions\": 1,\n" +
293 " \"optimizers\": [\"placement\"],\n" +
294 " \"timeout\": 600\n" +
296 " \"placementInfo\": {\n" +
297 " \"requestParameters\": {\n" +
298 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
299 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
300 " \"customerName\": \"${customerLocation.customerName}\"\n" +
302 " \"subscriberInfo\": { \n" +
303 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
304 " \"subscriberName\": \"${subscriberName}\",\n" +
305 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
307 " \"placementDemands\": [\n" +
308 " ${placementDemands}\n" +
311 " \"serviceInfo\": {\n" +
312 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
313 " \"serviceName\": \"${serviceName}\",\n" +
314 " \"modelInfo\": {\n" +
315 " \"modelType\": \"${modelType}\",\n" +
316 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
317 " \"modelVersionId\": \"${modelVersionId}\",\n" +
318 " \"modelName\": \"${modelName}\",\n" +
319 " \"modelVersion\": \"${modelVersion}\",\n" +
320 " \"modelCustomizationName\": \"\"\n" +
326 logger.debug( "Completed Building OOF Request")
328 } catch (Exception ex) {
329 logger.debug( "buildRequest Exception: " + ex)
334 * This method validates the callback response
335 * from OOF. If the response contains an
336 * exception the method will build and throw
337 * a workflow exception.
340 * @param response - the async callback response from oof
342 Void validateCallbackResponse(DelegateExecution execution, String response) {
343 String placements = ""
344 if (isBlank(response)) {
345 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
347 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
348 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
349 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
350 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
351 if (isBlank(statusMessage)) {
352 logger.debug( "Error Occurred in Homing: OOF Async Callback Response does " +
353 "not contain placement solution.")
354 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
355 "OOF Async Callback Response does not contain placement solution.")
357 logger.debug( "Error Occurred in Homing: " + statusMessage)
358 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
363 } else if (response.contains("error") || response.contains("Error") ) {
364 String errorMessage = ""
365 if (response.contains("policyException")) {
366 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
367 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
368 } else if (response.contains("Unable to find any candidate for demand")) {
369 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
370 "demand *** Response: " + response.toString()
371 } else if (response.contains("serviceException")) {
372 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
373 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
375 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
377 logger.debug( "Error Occurred in Homing: " + errorMessage)
378 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
381 logger.debug( "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.")
382 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
389 * This method creates candidates json for placement Demands.
392 * @param existingCandidates -
393 * @param excludedCandidates -
394 * @param requiredCandidates -
396 * @return candidatesJson - a JSON string with candidates
398 String createCandidateJson(ArrayList existingCandidates = null,
399 ArrayList excludedCandidates = null,
400 ArrayList requiredCandidates = null) {
401 def candidatesJson = ""
403 if (existingCandidates != null && existingCandidates != {}) {
404 sb = new StringBuilder()
406 " \"existingCandidates\": [\n")
407 def existingCandidateJson = ""
408 existingCandidates.each { existingCandidate ->
409 type = existingCandidate.get('identifierType')
410 if (type == 'vimId') {
411 def cloudOwner = existingCandidate.get('cloudOwner')
412 def cloudRegionId = existingCandidate.get('identifiers')
413 existingCandidateJson = "{\n" +
414 " \"identifierType\": \"vimId\",\n" +
415 " \"cloudOwner\": \"${cloudOwner}\",\n" +
416 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
418 sb.append(existingCandidateJson)
420 if (type == 'serviceInstanceId') {
421 def serviceInstanceId = existingCandidate.get('identifiers')
422 existingCandidateJson += "{\n" +
423 " \"identifierType\": \"serviceInstanceId\",\n" +
424 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
426 sb.append(existingCandidateJson)
429 if (existingCandidateJson != "") {
430 sb.setLength(sb.length() - 1)
431 candidatesJson = sb.append(",\n],")
434 if (excludedCandidates != null && excludedCandidates != {}) {
435 sb = new StringBuilder()
437 " \"excludedCandidates\": [\n")
438 def excludedCandidateJson = ""
439 excludedCandidates.each { excludedCandidate ->
440 type = excludedCandidate.get('identifierType')
441 if (type == 'vimId') {
442 def cloudOwner = excludedCandidate.get('cloudOwner')
443 def cloudRegionId = excludedCandidate.get('identifiers')
444 excludedCandidateJson = "{\n" +
445 " \"identifierType\": \"vimId\",\n" +
446 " \"cloudOwner\": \"${cloudOwner}\",\n" +
447 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
449 sb.append(excludedCandidateJson)
451 if (type == 'serviceInstanceId') {
452 def serviceInstanceId = excludedCandidate.get('identifiers')
453 excludedCandidateJson += "{\n" +
454 " \"identifierType\": \"serviceInstanceId\",\n" +
455 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
457 sb.append(excludedCandidateJson)
460 if (excludedCandidateJson != "") {
461 sb.setLength(sb.length() - 1)
462 candidatesJson = sb.append(",\n],")
465 if (requiredCandidates != null && requiredCandidates != {}) {
466 sb = new StringBuilder()
468 " \"requiredCandidates\": [\n")
469 def requiredCandidatesJson = ""
470 requiredCandidates.each { requiredCandidate ->
471 type = requiredCandidate.get('identifierType')
472 if (type == 'vimId') {
473 def cloudOwner = requiredCandidate.get('cloudOwner')
474 def cloudRegionId = requiredCandidate.get('identifiers')
475 requiredCandidatesJson = "{\n" +
476 " \"identifierType\": \"vimId\",\n" +
477 " \"cloudOwner\": \"${cloudOwner}\",\n" +
478 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
480 sb.append(requiredCandidatesJson)
482 if (type == 'serviceInstanceId') {
483 def serviceInstanceId = requiredCandidate.get('identifiers')
484 requiredCandidatesJson += "{\n" +
485 " \"identifierType\": \"serviceInstanceId\",\n" +
486 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
488 sb.append(requiredCandidatesJson)
491 if (requiredCandidatesJson != "") {
492 sb.setLength(sb.length() - 1)
493 candidatesJson = sb.append(",\n],")
496 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
497 return candidatesJson
501 * This method creates a cloudsite in catalog database.
503 * @param CloudSite cloudSite
507 Void createCloudSiteCatalogDb(CloudSite cloudSite, DelegateExecution execution) {
509 String endpoint = UrnPropertiesReader.getVariable("mso.catalog.db.spring.endpoint", execution)
510 String auth = UrnPropertiesReader.getVariable("mso.db.auth", execution)
511 String uri = "/cloudSite"
513 URL url = new URL(endpoint + uri)
514 HttpClient client = new HttpClientFactory().newJsonClient(url, TargetEntity.EXTERNAL)
515 client.addAdditionalHeader(HttpHeaders.AUTHORIZATION, auth)
516 client.addAdditionalHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
518 Response response = client.post(request.getBody().toString())
520 int responseCode = response.getStatus()
521 logDebug("CatalogDB response code is: " + responseCode)
522 String syncResponse = response.readEntity(String.class)
523 logDebug("CatalogDB response is: " + syncResponse)
525 if(responseCode != 202){
526 exceptionUtil.buildAndThrowWorkflowException(execution, responseCode, "Received a Bad Sync Response from CatalogDB.")
531 * This method creates a HomingInstance in catalog database.
533 * @param HomingInstance homingInstance
537 Void createHomingInstance(HomingInstance homingInstance, DelegateExecution execution) {
538 oofInfraUtils.createHomingInstance(homingInstance, execution)
540 String getMsbHost(DelegateExecution execution) {
541 String msbHost = UrnPropertiesReader.getVariable("mso.msb.host", execution, "msb-iag.onap")
543 Integer msbPort = UrnPropertiesReader.getVariable("mso.msb.port", execution, "80").toInteger()
545 return UriBuilder.fromPath("").host(msbHost).port(msbPort).scheme("http").build().toString()