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 resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
162 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
163 def resourceModelName = resource.getModelInfo().getModelName()
164 def resourceModelVersion = resource.getModelInfo().getModelVersion()
165 def resourceModelType = resource.getModelInfo().getModelType()
166 def tenantId = execution.getVariable("tenantId")
167 def requiredCandidatesJson = ""
169 requiredCandidatesJson = createCandidateJson(
176 " \"resourceModuleName\": \"${resourceModelName}\",\n" +
177 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
178 " \"tenantId\": \"${tenantId}\",\n" +
179 " \"resourceModelInfo\": {\n" +
180 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
181 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
182 " \"modelName\": \"${resourceModelName}\",\n" +
183 " \"modelType\": \"${resourceModelType}\",\n" +
184 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
185 " \"modelCustomizationName\": \"\"\n" +
186 " }" + requiredCandidatesJson + "\n" +
189 placementDemands = sb.append(demand)
193 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
194 utils.log("DEBUG", "VNF Resources List is empty",
198 for (VnfResource vnfResource : vnfResourceList) {
199 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
201 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
202 def serviceResourceId = vnfResource.getResourceId()
203 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
204 def resourceModelName = vnfResourceModelInfo.getModelName()
205 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
206 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
207 def resourceModelType = vnfResourceModelInfo.getModelType()
208 def tenantId = execution.getVariable("tenantId")
209 def requiredCandidatesJson = ""
212 String placementDemand =
214 " \"resourceModuleName\": \"${resourceModelName}\",\n" +
215 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
216 " \"tenantId\": \"${tenantId}\",\n" +
217 " \"resourceModelInfo\": {\n" +
218 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
219 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
220 " \"modelName\": \"${resourceModelName}\",\n" +
221 " \"modelType\": \"${resourceModelType}\",\n" +
222 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
223 " \"modelCustomizationName\": \"\"\n" +
224 " }" + requiredCandidatesJson + "\n" +
227 placementDemands = sb.append(placementDemand)
229 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
232 /* Commenting Out Licensing as OOF doesn't support for Beijing
233 String licenseDemands = ""
234 sb = new StringBuilder()
235 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
236 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
238 for (VnfResource vnfResource : vnfResourceList) {
239 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
240 def resourceInstanceType = vnfResource.getResourceType()
241 def serviceResourceId = vnfResource.getResourceId()
242 def resourceModuleName = vnfResource.getResourceType()
243 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
244 def resouceModelName = vnfResourceModelInfo.getModelName()
245 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
246 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
247 def resouceModelType = vnfResourceModelInfo.getModelType()
249 // TODO Add Existing Licenses to demand
250 //"existingLicenses": {
251 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
252 // "43257b49-9602-4fe5-9337-094e52bc9435"],
253 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
254 // "43257b49-9602-4fe5-9337-094e52bc9435"]
257 String licenseDemand =
259 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
260 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
261 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
262 "\"resourceModelInfo\": {\n" +
263 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
264 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
265 " \"modelName\": \"${resouceModelName}\",\n" +
266 " \"modelType\": \"${resouceModelType}\",\n" +
267 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
268 " \"modelCustomizationName\": \"\"\n" +
272 licenseDemands = sb.append(licenseDemand)
274 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
279 " \"requestInfo\": {\n" +
280 " \"transactionId\": \"${transactionId}\",\n" +
281 " \"requestId\": \"${requestId}\",\n" +
282 " \"callbackUrl\": \"${callbackUrl}\",\n" +
283 " \"sourceId\": \"so\",\n" +
284 " \"requestType\": \"${requestType}\"," +
285 " \"numSolutions\": 1,\n" +
286 " \"optimizers\": [\"placement\"],\n" +
287 " \"timeout\": 600\n" +
289 " \"placementInfo\": {\n" +
290 " \"requestParameters\": {\n" +
291 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
292 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
293 " \"customerName\": \"${customerLocation.customerName}\"\n" +
295 " \"subscriberInfo\": { \n" +
296 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
297 " \"subscriberName\": \"${subscriberName}\",\n" +
298 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
300 " \"placementDemands\": [\n" +
301 " ${placementDemands}\n" +
304 " \"serviceInfo\": {\n" +
305 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
306 " \"serviceName\": \"${serviceName}\",\n" +
307 " \"modelInfo\": {\n" +
308 " \"modelType\": \"${modelType}\",\n" +
309 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
310 " \"modelVersionId\": \"${modelVersionId}\",\n" +
311 " \"modelName\": \"${modelName}\",\n" +
312 " \"modelVersion\": \"${modelVersion}\",\n" +
313 " \"modelCustomizationName\": \"\"\n" +
319 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
321 } catch (Exception ex) {
322 utils.log("DEBUG", "buildRequest Exception: " + ex, isDebugEnabled)
327 * This method validates the callback response
328 * from OOF. If the response contains an
329 * exception the method will build and throw
330 * a workflow exception.
333 * @param response - the async callback response from oof
335 Void validateCallbackResponse(DelegateExecution execution, String response) {
336 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
337 String placements = ""
338 if (isBlank(response)) {
339 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
341 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
342 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
343 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
344 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
345 if (isBlank(statusMessage)) {
346 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
347 "not contain placement solution.", isDebugEnabled)
348 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
349 "OOF Async Callback Response does not contain placement solution.")
351 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
352 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
357 } else if (response.contains("error") || response.contains("Error") ) {
358 String errorMessage = ""
359 if (response.contains("policyException")) {
360 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
361 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
362 } else if (response.contains("Unable to find any candidate for demand")) {
363 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
364 "demand *** Response: " + response.toString()
365 } else if (response.contains("serviceException")) {
366 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
367 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
369 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
371 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
372 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
375 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
376 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
383 * This method creates candidates json for placement Demands.
386 * @param existingCandidates -
387 * @param excludedCandidates -
388 * @param requiredCandidates -
390 * @return candidatesJson - a JSON string with candidates
392 String createCandidateJson(ArrayList existingCandidates = null,
393 ArrayList excludedCandidates = null,
394 ArrayList requiredCandidates = null) {
395 def candidatesJson = ""
397 if (existingCandidates != null && existingCandidates != {}) {
398 sb = new StringBuilder()
400 " \"existingCandidates\": [\n")
401 def existingCandidateJson = ""
402 existingCandidates.each { existingCandidate ->
403 type = existingCandidate.get('identifierType')
404 if (type == 'vimId') {
405 def cloudOwner = existingCandidate.get('cloudOwner')
406 def cloudRegionId = existingCandidate.get('identifiers')
407 existingCandidateJson = "{\n" +
408 " \"identifierType\": \"vimId\",\n" +
409 " \"cloudOwner\": \"${cloudOwner}\",\n" +
410 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
412 sb.append(existingCandidateJson)
414 if (type == 'serviceInstanceId') {
415 def serviceInstanceId = existingCandidate.get('identifiers')
416 existingCandidateJson += "{\n" +
417 " \"identifierType\": \"serviceInstanceId\",\n" +
418 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
420 sb.append(existingCandidateJson)
423 if (existingCandidateJson != "") {
424 sb.setLength(sb.length() - 1)
425 candidatesJson = sb.append(",\n],")
428 if (excludedCandidates != null && excludedCandidates != {}) {
429 sb = new StringBuilder()
431 " \"excludedCandidates\": [\n")
432 def excludedCandidateJson = ""
433 excludedCandidates.each { excludedCandidate ->
434 type = excludedCandidate.get('identifierType')
435 if (type == 'vimId') {
436 def cloudOwner = excludedCandidate.get('cloudOwner')
437 def cloudRegionId = excludedCandidate.get('identifiers')
438 excludedCandidateJson = "{\n" +
439 " \"identifierType\": \"vimId\",\n" +
440 " \"cloudOwner\": \"${cloudOwner}\",\n" +
441 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
443 sb.append(excludedCandidateJson)
445 if (type == 'serviceInstanceId') {
446 def serviceInstanceId = excludedCandidate.get('identifiers')
447 excludedCandidateJson += "{\n" +
448 " \"identifierType\": \"serviceInstanceId\",\n" +
449 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
451 sb.append(excludedCandidateJson)
454 if (excludedCandidateJson != "") {
455 sb.setLength(sb.length() - 1)
456 candidatesJson = sb.append(",\n],")
459 if (requiredCandidates != null && requiredCandidates != {}) {
460 sb = new StringBuilder()
462 " \"requiredCandidates\": [\n")
463 def requiredCandidatesJson = ""
464 requiredCandidates.each { requiredCandidate ->
465 type = requiredCandidate.get('identifierType')
466 if (type == 'vimId') {
467 def cloudOwner = requiredCandidate.get('cloudOwner')
468 def cloudRegionId = requiredCandidate.get('identifiers')
469 requiredCandidatesJson = "{\n" +
470 " \"identifierType\": \"vimId\",\n" +
471 " \"cloudOwner\": \"${cloudOwner}\",\n" +
472 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
474 sb.append(requiredCandidatesJson)
476 if (type == 'serviceInstanceId') {
477 def serviceInstanceId = requiredCandidate.get('identifiers')
478 requiredCandidatesJson += "{\n" +
479 " \"identifierType\": \"serviceInstanceId\",\n" +
480 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
482 sb.append(requiredCandidatesJson)
485 if (requiredCandidatesJson != "") {
486 sb.setLength(sb.length() - 1)
487 candidatesJson = sb.append(",\n],")
490 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
491 return candidatesJson
494 * This method creates a cloudsite in catalog database.
496 * @param CloudSite cloudSite
500 Void createCloudSiteCatalogDb(CloudSite cloudSite, DelegateExecution execution) {
502 String endpoint = UrnPropertiesReader.getVariable("mso.catalog.db.spring.endpoint", execution)
503 String auth = UrnPropertiesReader.getVariable("mso.db.auth", execution)
504 String uri = "/cloudSite"
506 URL url = new URL(endpoint + uri)
507 HttpClient client = new HttpClient(url, MediaType.APPLICATION_JSON, TargetEntity.EXTERNAL)
508 client.addAdditionalHeader(HttpHeaders.AUTHORIZATION, auth)
509 client.addAdditionalHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
511 Response response = client.post(request.getBody().toString())
513 int responseCode = response.getStatus()
514 logDebug("CatalogDB response code is: " + responseCode, isDebugEnabled)
515 String syncResponse = response.readEntity(String.class)
516 logDebug("CatalogDB response is: " + syncResponse, isDebugEnabled)
518 if(responseCode != 202){
519 exceptionUtil.buildAndThrowWorkflowException(execution, responseCode, "Received a Bad Sync Response from CatalogDB.")