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 resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
163 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
164 def resourceModelName = resource.getModelInfo().getModelName()
165 def resourceModelVersion = resource.getModelInfo().getModelVersion()
166 def resourceModelType = resource.getModelInfo().getModelType()
167 def tenantId = execution.getVariable("tenantId")
168 def requiredCandidatesJson = ""
170 requiredCandidatesJson = createCandidateJson(
177 " \"resourceModuleName\": \"${resourceModelName}\",\n" +
178 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
179 " \"tenantId\": \"${tenantId}\",\n" +
180 " \"resourceModelInfo\": {\n" +
181 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
182 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
183 " \"modelName\": \"${resourceModelName}\",\n" +
184 " \"modelType\": \"${resourceModelType}\",\n" +
185 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
186 " \"modelCustomizationName\": \"\"\n" +
187 " }" + requiredCandidatesJson + "\n" +
190 placementDemands = sb.append(demand)
194 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
195 utils.log("DEBUG", "VNF Resources List is empty",
199 for (VnfResource vnfResource : vnfResourceList) {
200 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
202 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
203 def serviceResourceId = vnfResource.getResourceId()
204 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
205 def resourceModelName = vnfResourceModelInfo.getModelName()
206 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
207 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
208 def resourceModelType = vnfResourceModelInfo.getModelType()
209 def tenantId = execution.getVariable("tenantId")
210 def requiredCandidatesJson = ""
213 String placementDemand =
215 " \"resourceModuleName\": \"${resourceModelName}\",\n" +
216 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
217 " \"tenantId\": \"${tenantId}\",\n" +
218 " \"resourceModelInfo\": {\n" +
219 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
220 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
221 " \"modelName\": \"${resourceModelName}\",\n" +
222 " \"modelType\": \"${resourceModelType}\",\n" +
223 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
224 " \"modelCustomizationName\": \"\"\n" +
225 " }" + requiredCandidatesJson + "\n" +
228 placementDemands = sb.append(placementDemand)
230 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
233 /* Commenting Out Licensing as OOF doesn't support for Beijing
234 String licenseDemands = ""
235 sb = new StringBuilder()
236 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
237 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
239 for (VnfResource vnfResource : vnfResourceList) {
240 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
241 def resourceInstanceType = vnfResource.getResourceType()
242 def serviceResourceId = vnfResource.getResourceId()
243 def resourceModuleName = vnfResource.getResourceType()
244 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
245 def resouceModelName = vnfResourceModelInfo.getModelName()
246 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
247 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
248 def resouceModelType = vnfResourceModelInfo.getModelType()
250 // TODO Add Existing Licenses to demand
251 //"existingLicenses": {
252 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
253 // "43257b49-9602-4fe5-9337-094e52bc9435"],
254 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
255 // "43257b49-9602-4fe5-9337-094e52bc9435"]
258 String licenseDemand =
260 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
261 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
262 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
263 "\"resourceModelInfo\": {\n" +
264 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
265 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
266 " \"modelName\": \"${resouceModelName}\",\n" +
267 " \"modelType\": \"${resouceModelType}\",\n" +
268 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
269 " \"modelCustomizationName\": \"\"\n" +
273 licenseDemands = sb.append(licenseDemand)
275 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
280 " \"requestInfo\": {\n" +
281 " \"transactionId\": \"${transactionId}\",\n" +
282 " \"requestId\": \"${requestId}\",\n" +
283 " \"callbackUrl\": \"${callbackUrl}\",\n" +
284 " \"sourceId\": \"so\",\n" +
285 " \"requestType\": \"${requestType}\"," +
286 " \"numSolutions\": 1,\n" +
287 " \"optimizers\": [\"placement\"],\n" +
288 " \"timeout\": 600\n" +
290 " \"placementInfo\": {\n" +
291 " \"requestParameters\": {\n" +
292 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
293 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
294 " \"customerName\": \"${customerLocation.customerName}\"\n" +
296 " \"subscriberInfo\": { \n" +
297 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
298 " \"subscriberName\": \"${subscriberName}\",\n" +
299 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
301 " \"placementDemands\": [\n" +
302 " ${placementDemands}\n" +
305 " \"serviceInfo\": {\n" +
306 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
307 " \"serviceName\": \"${serviceName}\",\n" +
308 " \"modelInfo\": {\n" +
309 " \"modelType\": \"${modelType}\",\n" +
310 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
311 " \"modelVersionId\": \"${modelVersionId}\",\n" +
312 " \"modelName\": \"${modelName}\",\n" +
313 " \"modelVersion\": \"${modelVersion}\",\n" +
314 " \"modelCustomizationName\": \"\"\n" +
320 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
322 } catch (Exception ex) {
323 utils.log("DEBUG", "buildRequest Exception: " + ex, isDebugEnabled)
328 * This method validates the callback response
329 * from OOF. If the response contains an
330 * exception the method will build and throw
331 * a workflow exception.
334 * @param response - the async callback response from oof
336 Void validateCallbackResponse(DelegateExecution execution, String response) {
337 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
338 String placements = ""
339 if (isBlank(response)) {
340 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
342 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
343 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
344 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
345 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
346 if (isBlank(statusMessage)) {
347 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
348 "not contain placement solution.", isDebugEnabled)
349 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
350 "OOF Async Callback Response does not contain placement solution.")
352 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
353 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
358 } else if (response.contains("error") || response.contains("Error") ) {
359 String errorMessage = ""
360 if (response.contains("policyException")) {
361 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
362 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
363 } else if (response.contains("Unable to find any candidate for demand")) {
364 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
365 "demand *** Response: " + response.toString()
366 } else if (response.contains("serviceException")) {
367 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
368 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
370 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
372 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
373 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
376 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
377 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
384 * This method creates candidates json for placement Demands.
387 * @param existingCandidates -
388 * @param excludedCandidates -
389 * @param requiredCandidates -
391 * @return candidatesJson - a JSON string with candidates
393 String createCandidateJson(ArrayList existingCandidates = null,
394 ArrayList excludedCandidates = null,
395 ArrayList requiredCandidates = null) {
396 def candidatesJson = ""
398 if (existingCandidates != null && existingCandidates != {}) {
399 sb = new StringBuilder()
401 " \"existingCandidates\": [\n")
402 def existingCandidateJson = ""
403 existingCandidates.each { existingCandidate ->
404 type = existingCandidate.get('identifierType')
405 if (type == 'vimId') {
406 def cloudOwner = existingCandidate.get('cloudOwner')
407 def cloudRegionId = existingCandidate.get('identifiers')
408 existingCandidateJson = "{\n" +
409 " \"identifierType\": \"vimId\",\n" +
410 " \"cloudOwner\": \"${cloudOwner}\",\n" +
411 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
413 sb.append(existingCandidateJson)
415 if (type == 'serviceInstanceId') {
416 def serviceInstanceId = existingCandidate.get('identifiers')
417 existingCandidateJson += "{\n" +
418 " \"identifierType\": \"serviceInstanceId\",\n" +
419 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
421 sb.append(existingCandidateJson)
424 if (existingCandidateJson != "") {
425 sb.setLength(sb.length() - 1)
426 candidatesJson = sb.append(",\n],")
429 if (excludedCandidates != null && excludedCandidates != {}) {
430 sb = new StringBuilder()
432 " \"excludedCandidates\": [\n")
433 def excludedCandidateJson = ""
434 excludedCandidates.each { excludedCandidate ->
435 type = excludedCandidate.get('identifierType')
436 if (type == 'vimId') {
437 def cloudOwner = excludedCandidate.get('cloudOwner')
438 def cloudRegionId = excludedCandidate.get('identifiers')
439 excludedCandidateJson = "{\n" +
440 " \"identifierType\": \"vimId\",\n" +
441 " \"cloudOwner\": \"${cloudOwner}\",\n" +
442 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
444 sb.append(excludedCandidateJson)
446 if (type == 'serviceInstanceId') {
447 def serviceInstanceId = excludedCandidate.get('identifiers')
448 excludedCandidateJson += "{\n" +
449 " \"identifierType\": \"serviceInstanceId\",\n" +
450 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
452 sb.append(excludedCandidateJson)
455 if (excludedCandidateJson != "") {
456 sb.setLength(sb.length() - 1)
457 candidatesJson = sb.append(",\n],")
460 if (requiredCandidates != null && requiredCandidates != {}) {
461 sb = new StringBuilder()
463 " \"requiredCandidates\": [\n")
464 def requiredCandidatesJson = ""
465 requiredCandidates.each { requiredCandidate ->
466 type = requiredCandidate.get('identifierType')
467 if (type == 'vimId') {
468 def cloudOwner = requiredCandidate.get('cloudOwner')
469 def cloudRegionId = requiredCandidate.get('identifiers')
470 requiredCandidatesJson = "{\n" +
471 " \"identifierType\": \"vimId\",\n" +
472 " \"cloudOwner\": \"${cloudOwner}\",\n" +
473 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
475 sb.append(requiredCandidatesJson)
477 if (type == 'serviceInstanceId') {
478 def serviceInstanceId = requiredCandidate.get('identifiers')
479 requiredCandidatesJson += "{\n" +
480 " \"identifierType\": \"serviceInstanceId\",\n" +
481 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
483 sb.append(requiredCandidatesJson)
486 if (requiredCandidatesJson != "") {
487 sb.setLength(sb.length() - 1)
488 candidatesJson = sb.append(",\n],")
491 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
492 return candidatesJson
495 * This method creates a cloudsite in catalog database.
497 * @param CloudSite cloudSite
501 Void createCloudSiteCatalogDb(CloudSite cloudSite, DelegateExecution execution) {
503 String endpoint = UrnPropertiesReader.getVariable("mso.catalog.db.spring.endpoint", execution)
504 String auth = UrnPropertiesReader.getVariable("mso.db.auth", execution)
505 String uri = "/cloudSite"
507 HttpHeaders headers = new HttpHeaders()
509 headers.set(HttpHeaders.AUTHORIZATION, auth)
510 headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
511 headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
513 UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(endpoint + uri)
514 HttpEntity<CloudSite> request = new HttpEntity<CloudSite>(cloudSite, headers)
515 RESTConfig config = new RESTConfig(endpoint + uri)
516 RESTClient client = new RESTClient(config).addAuthorizationHeader(auth).
517 addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON).addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
518 APIResponse response = client.httpPost(request.getBody().toString())
520 int responseCode = response.getStatusCode()
521 logDebug("CatalogDB response code is: " + responseCode, isDebugEnabled)
522 String syncResponse = response.getResponseBodyAsString()
523 logDebug("CatalogDB response is: " + syncResponse, isDebugEnabled)
525 if(responseCode != 202){
526 exceptionUtil.buildAndThrowWorkflowException(execution, responseCode, "Received a Bad Sync Response from CatalogDB.")