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 def callbackUrl = utils.createWorkflowMessageAdapterCallbackURL(execution, "oofResponse", requestId)
91 def transactionId = requestId
92 //ServiceInstance Info
93 ServiceInstance serviceInstance = decomposition.getServiceInstance()
94 def serviceInstanceId = ""
97 serviceInstanceId = execution.getVariable("serviceInstanceId")
98 serviceName = execution.getVariable("subscriptionServiceType")
100 if (serviceInstanceId == null || serviceInstanceId == "null") {
101 utils.log("DEBUG", "Unable to obtain Service Instance Id", isDebugEnabled)
102 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
103 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
105 if (serviceName == null || serviceName == "null") {
106 utils.log("DEBUG", "Unable to obtain Service Name", isDebugEnabled)
107 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
108 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
111 ModelInfo model = decomposition.getModelInfo()
112 String modelType = model.getModelType()
113 String modelInvariantId = model.getModelInvariantUuid()
114 String modelVersionId = model.getModelUuid()
115 String modelName = model.getModelName()
116 String modelVersion = model.getModelVersion()
118 String subscriberId = ""
119 String subscriberName = ""
120 String commonSiteId = ""
121 if (subscriber != null){
122 subscriberId = subscriber.getGlobalId()
123 subscriberName = subscriber.getName()
124 commonSiteId = subscriber.getCommonSiteId()
127 //Determine RequestType
128 //TODO Figure out better way to determine this
129 String requestType = "create"
130 List<Resource> resources = decomposition.getServiceResources()
131 for(Resource r:resources){
132 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
133 if(currentSolution != null){
134 requestType = "speed changed"
139 String placementDemands = ""
140 StringBuilder sb = new StringBuilder()
141 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
142 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
144 if (allottedResourceList == null || allottedResourceList.isEmpty() ) {
145 utils.log("DEBUG", "Allotted Resources List is empty - will try to get service VNFs instead.",
147 allottedResourceList = decomposition.getVnfResources()
150 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
151 utils.log("DEBUG", "Resources List is Empty", isDebugEnabled)
153 for (AllottedResource resource : allottedResourceList) {
154 utils.log("DEBUG", "Allotted Resource: " + resource.toString(),
156 def serviceResourceId = resource.getResourceId()
157 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
158 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
159 def resourceModelName = resource.getModelInfo().getModelName()
160 def resourceModelVersion = resource.getModelInfo().getModelVersion()
161 def resourceModelType = resource.getModelInfo().getModelType()
162 def tenantId = execution.getVariable("tenantId")
163 def requiredCandidatesJson = ""
165 requiredCandidatesJson = createCandidateJson(
172 " \"resourceModuleName\": \"${resourceModelName}\",\n" +
173 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
174 " \"tenantId\": \"${tenantId}\",\n" +
175 " \"resourceModelInfo\": {\n" +
176 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
177 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
178 " \"modelName\": \"${resourceModelName}\",\n" +
179 " \"modelType\": \"${resourceModelType}\",\n" +
180 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
181 " \"modelCustomizationName\": \"\"\n" +
182 " }" + requiredCandidatesJson + "\n" +
185 placementDemands = sb.append(demand)
187 for (VnfResource vnfResource : vnfResourceList) {
188 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
190 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
191 def serviceResourceId = vnfResource.getResourceId()
192 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
193 def resourceModelName = vnfResourceModelInfo.getModelName()
194 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
195 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
196 def resourceModelType = vnfResourceModelInfo.getModelType()
197 def tenantId = execution.getVariable("tenantId")
198 def requiredCandidatesJson = ""
201 String placementDemand =
203 " \"resourceModuleName\": \"${resourceModelName}\",\n" +
204 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
205 " \"tenantId\": \"${tenantId}\",\n" +
206 " \"resourceModelInfo\": {\n" +
207 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
208 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
209 " \"modelName\": \"${resourceModelName}\",\n" +
210 " \"modelType\": \"${resourceModelType}\",\n" +
211 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
212 " \"modelCustomizationName\": \"\"\n" +
213 " }" + requiredCandidatesJson + "\n" +
216 placementDemands = sb.append(placementDemand)
218 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
221 /* Commenting Out Licensing as OOF doesn't support for Beijing
222 String licenseDemands = ""
223 sb = new StringBuilder()
224 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
225 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
227 for (VnfResource vnfResource : vnfResourceList) {
228 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
229 def resourceInstanceType = vnfResource.getResourceType()
230 def serviceResourceId = vnfResource.getResourceId()
231 def resourceModuleName = vnfResource.getResourceType()
232 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
233 def resouceModelName = vnfResourceModelInfo.getModelName()
234 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
235 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
236 def resouceModelType = vnfResourceModelInfo.getModelType()
238 // TODO Add Existing Licenses to demand
239 //"existingLicenses": {
240 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
241 // "43257b49-9602-4fe5-9337-094e52bc9435"],
242 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
243 // "43257b49-9602-4fe5-9337-094e52bc9435"]
246 String licenseDemand =
248 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
249 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
250 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
251 "\"resourceModelInfo\": {\n" +
252 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
253 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
254 " \"modelName\": \"${resouceModelName}\",\n" +
255 " \"modelType\": \"${resouceModelType}\",\n" +
256 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
257 " \"modelCustomizationName\": \"\"\n" +
261 licenseDemands = sb.append(licenseDemand)
263 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
268 " \"requestInfo\": {\n" +
269 " \"transactionId\": \"${transactionId}\",\n" +
270 " \"requestId\": \"${requestId}\",\n" +
271 " \"callbackUrl\": \"${callbackUrl}\",\n" +
272 " \"sourceId\": \"so\",\n" +
273 " \"requestType\": \"${requestType}\"," +
274 " \"numSolutions\": 1,\n" +
275 " \"optimizers\": [\"placement\"],\n" +
276 " \"timeout\": 600\n" +
278 " \"placementInfo\": {\n" +
279 " \"requestParameters\": {\n" +
280 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
281 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
282 " \"customerName\": \"${customerLocation.customerName}\"\n" +
284 " \"subscriberInfo\": { \n" +
285 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
286 " \"subscriberName\": \"${subscriberName}\",\n" +
287 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
289 " \"placementDemands\": [\n" +
290 " ${placementDemands}\n" +
293 " \"serviceInfo\": {\n" +
294 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
295 " \"serviceName\": \"${serviceName}\",\n" +
296 " \"modelInfo\": {\n" +
297 " \"modelType\": \"${modelType}\",\n" +
298 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
299 " \"modelVersionId\": \"${modelVersionId}\",\n" +
300 " \"modelName\": \"${modelName}\",\n" +
301 " \"modelVersion\": \"${modelVersion}\",\n" +
302 " \"modelCustomizationName\": \"\"\n" +
308 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
313 * This method validates the callback response
314 * from OOF. If the response contains an
315 * exception the method will build and throw
316 * a workflow exception.
319 * @param response - the async callback response from oof
321 Void validateCallbackResponse(DelegateExecution execution, String response) {
322 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
323 String placements = ""
324 if (isBlank(response)) {
325 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
327 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
328 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
329 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
330 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
331 if (isBlank(statusMessage)) {
332 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
333 "not contain placement solution.", isDebugEnabled)
334 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
335 "OOF Async Callback Response does not contain placement solution.")
337 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
338 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
343 } else if (response.contains("error") || response.contains("Error") ) {
344 String errorMessage = ""
345 if (response.contains("policyException")) {
346 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
347 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
348 } else if (response.contains("Unable to find any candidate for demand")) {
349 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
350 "demand *** Response: " + response.toString()
351 } else if (response.contains("serviceException")) {
352 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
353 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
355 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
357 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
358 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
361 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
362 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
369 * This method creates candidates json for placement Demands.
372 * @param existingCandidates -
373 * @param excludedCandidates -
374 * @param requiredCandidates -
376 * @return candidatesJson - a JSON string with candidates
378 String createCandidateJson(ArrayList existingCandidates = null,
379 ArrayList excludedCandidates = null,
380 ArrayList requiredCandidates = null) {
381 def candidatesJson = ""
383 if (existingCandidates != null && existingCandidates != {}) {
384 sb = new StringBuilder()
386 " \"existingCandidates\": [\n")
387 def existingCandidateJson = ""
388 existingCandidates.each { existingCandidate ->
389 type = existingCandidate.get('identifierType')
390 if (type == 'vimId') {
391 def cloudOwner = existingCandidate.get('cloudOwner')
392 def cloudRegionId = existingCandidate.get('identifiers')
393 existingCandidateJson = "{\n" +
394 " \"identifierType\": \"vimId\",\n" +
395 " \"cloudOwner\": \"${cloudOwner}\",\n" +
396 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
398 sb.append(existingCandidateJson)
400 if (type == 'serviceInstanceId') {
401 def serviceInstanceId = existingCandidate.get('identifiers')
402 existingCandidateJson += "{\n" +
403 " \"identifierType\": \"serviceInstanceId\",\n" +
404 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
406 sb.append(existingCandidateJson)
409 if (existingCandidateJson != "") {
410 sb.setLength(sb.length() - 1)
411 candidatesJson = sb.append(",\n],")
414 if (excludedCandidates != null && excludedCandidates != {}) {
415 sb = new StringBuilder()
417 " \"excludedCandidates\": [\n")
418 def excludedCandidateJson = ""
419 excludedCandidates.each { excludedCandidate ->
420 type = excludedCandidate.get('identifierType')
421 if (type == 'vimId') {
422 def cloudOwner = excludedCandidate.get('cloudOwner')
423 def cloudRegionId = excludedCandidate.get('identifiers')
424 excludedCandidateJson = "{\n" +
425 " \"identifierType\": \"vimId\",\n" +
426 " \"cloudOwner\": \"${cloudOwner}\",\n" +
427 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
429 sb.append(excludedCandidateJson)
431 if (type == 'serviceInstanceId') {
432 def serviceInstanceId = excludedCandidate.get('identifiers')
433 excludedCandidateJson += "{\n" +
434 " \"identifierType\": \"serviceInstanceId\",\n" +
435 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
437 sb.append(excludedCandidateJson)
440 if (excludedCandidateJson != "") {
441 sb.setLength(sb.length() - 1)
442 candidatesJson = sb.append(",\n],")
445 if (requiredCandidates != null && requiredCandidates != {}) {
446 sb = new StringBuilder()
448 " \"requiredCandidates\": [\n")
449 def requiredCandidatesJson = ""
450 requiredCandidates.each { requiredCandidate ->
451 type = requiredCandidate.get('identifierType')
452 if (type == 'vimId') {
453 def cloudOwner = requiredCandidate.get('cloudOwner')
454 def cloudRegionId = requiredCandidate.get('identifiers')
455 requiredCandidatesJson = "{\n" +
456 " \"identifierType\": \"vimId\",\n" +
457 " \"cloudOwner\": \"${cloudOwner}\",\n" +
458 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
460 sb.append(requiredCandidatesJson)
462 if (type == 'serviceInstanceId') {
463 def serviceInstanceId = requiredCandidate.get('identifiers')
464 requiredCandidatesJson += "{\n" +
465 " \"identifierType\": \"serviceInstanceId\",\n" +
466 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
468 sb.append(requiredCandidatesJson)
471 if (requiredCandidatesJson != "") {
472 sb.setLength(sb.length() - 1)
473 candidatesJson = sb.append(",\n],")
476 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
477 return candidatesJson
480 * This method creates a cloudsite in catalog database.
482 * @param CloudSite cloudSite
486 Void createCloudSiteCatalogDb(CloudSite cloudSite, DelegateExecution execution) {
488 String endpoint = UrnPropertiesReader.getVariable("mso.catalog.db.spring.endpoint", execution)
489 String auth = UrnPropertiesReader.getVariable("mso.db.auth", execution)
490 String uri = "/cloudSite"
492 HttpHeaders headers = new HttpHeaders()
494 headers.set(HttpHeaders.AUTHORIZATION, auth)
495 headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
496 headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
498 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(endpoint + uri)
499 HttpEntity<CloudSite> request = new HttpEntity<CloudSite>(cloudSite, headers)
500 RESTConfig config = new RESTConfig(endpoint + uri)
501 RESTClient client = new RESTClient(config).addAuthorizationHeader(auth).
502 addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON).addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
503 APIResponse response = client.httpPost(request.getBody().toString())
505 int responseCode = response.getStatusCode()
506 logDebug("CatalogDB response code is: " + responseCode, isDebugEnabled)
507 String syncResponse = response.getResponseBodyAsString()
508 logDebug("CatalogDB response is: " + syncResponse, isDebugEnabled)
510 if(responseCode != 202){
511 exceptionUtil.buildAndThrowWorkflowException(execution, responseCode, "Received a Bad Sync Response from CatalogDB.")