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.client.HttpClientFactory
38 import org.onap.so.db.catalog.beans.CloudSite
39 import org.onap.so.db.catalog.beans.HomingInstance
40 import org.onap.so.utils.TargetEntity
41 import org.springframework.http.HttpEntity
42 import org.springframework.http.HttpHeaders
43 import org.springframework.http.HttpMethod
44 import org.springframework.http.ResponseEntity
45 import org.springframework.http.client.BufferingClientHttpRequestFactory
46 import org.springframework.http.client.HttpComponentsClientHttpRequestFactory
47 import org.springframework.web.client.RestTemplate
48 import org.springframework.web.util.UriComponentsBuilder
50 import javax.ws.rs.core.MediaType
51 import javax.ws.rs.core.Response
52 import javax.xml.ws.http.HTTPException
54 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
57 ExceptionUtil exceptionUtil = new ExceptionUtil()
58 JsonUtils jsonUtil = new JsonUtils()
60 private AbstractServiceTaskProcessor utils
62 OofUtils(AbstractServiceTaskProcessor taskProcessor) {
63 this.utils = taskProcessor
67 * This method builds the service-agnostic
68 * OOF json request to get a homing solution
69 * and license solution
73 * @param decomposition - ServiceDecomposition object
74 * @param customerLocation -
75 * @param existingCandidates -
76 * @param excludedCandidates -
77 * @param requiredCandidates -
79 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
81 String buildRequest(DelegateExecution execution,
83 ServiceDecomposition decomposition,
84 Subscriber subscriber = null,
86 ArrayList existingCandidates = null,
87 ArrayList excludedCandidates = null,
88 ArrayList requiredCandidates = null) {
89 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
90 utils.log("DEBUG", "Started Building OOF Request", isDebugEnabled)
91 String callbackEndpoint = UrnPropertiesReader.getVariable("mso.oof.callbackEndpoint", execution)
92 utils.log("DEBUG", "mso.oof.callbackEndpoint is: " + callbackEndpoint, isDebugEnabled)
94 def callbackUrl = utils.createHomingCallbackURL(callbackEndpoint, "oofResponse", requestId)
95 utils.log("DEBUG", "callbackUrl is: " + callbackUrl, isDebugEnabled)
98 def transactionId = requestId
99 utils.log("DEBUG", "transactionId is: " + transactionId, isDebugEnabled)
100 //ServiceInstance Info
101 ServiceInstance serviceInstance = decomposition.getServiceInstance()
102 def serviceInstanceId = ""
105 serviceInstanceId = execution.getVariable("serviceInstanceId")
106 utils.log("DEBUG", "serviceInstanceId is: " + serviceInstanceId, isDebugEnabled)
107 serviceName = execution.getVariable("subscriptionServiceType")
108 utils.log("DEBUG", "serviceName is: " + serviceName, isDebugEnabled)
110 if (serviceInstanceId == null || serviceInstanceId == "null") {
111 utils.log("DEBUG", "Unable to obtain Service Instance Id", isDebugEnabled)
112 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
113 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceId\") is null")
115 if (serviceName == null || serviceName == "null") {
116 utils.log("DEBUG", "Unable to obtain Service Name", isDebugEnabled)
117 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
118 "obtain Service Name, execution.getVariable(\"subscriptionServiceType\") is null")
121 ModelInfo model = decomposition.getModelInfo()
122 utils.log("DEBUG", "ModelInfo: " + model.toString(), isDebugEnabled)
123 String modelType = model.getModelType()
124 String modelInvariantId = model.getModelInvariantUuid()
125 String modelVersionId = model.getModelUuid()
126 String modelName = model.getModelName()
127 String modelVersion = model.getModelVersion()
129 String subscriberId = ""
130 String subscriberName = ""
131 String commonSiteId = ""
132 if (subscriber != null) {
133 subscriberId = subscriber.getGlobalId()
134 subscriberName = subscriber.getName()
135 commonSiteId = subscriber.getCommonSiteId()
138 //Determine RequestType
139 //TODO Figure out better way to determine this
140 String requestType = "create"
141 List<Resource> resources = decomposition.getServiceResources()
142 for (Resource r : resources) {
143 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
144 if (currentSolution != null) {
145 requestType = "speed changed"
150 String placementDemands = ""
151 StringBuilder sb = new StringBuilder()
152 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
153 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
155 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
156 utils.log("DEBUG", "Allotted Resources List is empty - will try to get service VNFs instead.",
159 for (AllottedResource resource : allottedResourceList) {
160 utils.log("DEBUG", "Allotted Resource: " + resource.toString(),
162 def serviceResourceId = resource.getResourceId()
163 def toscaNodeType = resource.getToscaNodeType()
164 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
165 def resourceModelInvariantId = resource.getModelInfo().getModelInvariantUuid()
166 def resourceModelVersionId = resource.getModelInfo().getModelUuid()
167 def resourceModelName = resource.getModelInfo().getModelName()
168 def resourceModelVersion = resource.getModelInfo().getModelVersion()
169 def resourceModelType = resource.getModelInfo().getModelType()
170 def tenantId = execution.getVariable("tenantId")
171 def requiredCandidatesJson = ""
173 requiredCandidatesJson = createCandidateJson(
180 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
181 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
182 " \"tenantId\": \"${tenantId}\",\n" +
183 " \"resourceModelInfo\": {\n" +
184 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
185 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
186 " \"modelName\": \"${resourceModelName}\",\n" +
187 " \"modelType\": \"${resourceModelType}\",\n" +
188 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
189 " \"modelCustomizationName\": \"\"\n" +
190 " }" + requiredCandidatesJson + "\n" +
193 placementDemands = sb.append(demand)
197 if (vnfResourceList == null || vnfResourceList.isEmpty()) {
198 utils.log("DEBUG", "VNF Resources List is empty",
202 for (VnfResource vnfResource : vnfResourceList) {
203 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
205 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
206 def toscaNodeType = vnfResource.getToscaNodeType()
207 def resourceModuleName = toscaNodeType.substring(toscaNodeType.lastIndexOf(".") + 1)
208 def serviceResourceId = vnfResource.getResourceId()
209 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
210 def resourceModelName = vnfResourceModelInfo.getModelName()
211 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
212 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
213 def resourceModelType = vnfResourceModelInfo.getModelType()
214 def tenantId = execution.getVariable("tenantId")
215 def requiredCandidatesJson = ""
218 String placementDemand =
220 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
221 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
222 " \"tenantId\": \"${tenantId}\",\n" +
223 " \"resourceModelInfo\": {\n" +
224 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
225 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
226 " \"modelName\": \"${resourceModelName}\",\n" +
227 " \"modelType\": \"${resourceModelType}\",\n" +
228 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
229 " \"modelCustomizationName\": \"\"\n" +
230 " }" + requiredCandidatesJson + "\n" +
233 placementDemands = sb.append(placementDemand)
235 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
238 /* Commenting Out Licensing as OOF doesn't support for Beijing
239 String licenseDemands = ""
240 sb = new StringBuilder()
241 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
242 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
244 for (VnfResource vnfResource : vnfResourceList) {
245 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
246 def resourceInstanceType = vnfResource.getResourceType()
247 def serviceResourceId = vnfResource.getResourceId()
248 def resourceModuleName = vnfResource.getResourceType()
249 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
250 def resouceModelName = vnfResourceModelInfo.getModelName()
251 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
252 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
253 def resouceModelType = vnfResourceModelInfo.getModelType()
255 // TODO Add Existing Licenses to demand
256 //"existingLicenses": {
257 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
258 // "43257b49-9602-4fe5-9337-094e52bc9435"],
259 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
260 // "43257b49-9602-4fe5-9337-094e52bc9435"]
263 String licenseDemand =
265 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
266 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
267 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
268 "\"resourceModelInfo\": {\n" +
269 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
270 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
271 " \"modelName\": \"${resouceModelName}\",\n" +
272 " \"modelType\": \"${resouceModelType}\",\n" +
273 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
274 " \"modelCustomizationName\": \"\"\n" +
278 licenseDemands = sb.append(licenseDemand)
280 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
285 " \"requestInfo\": {\n" +
286 " \"transactionId\": \"${transactionId}\",\n" +
287 " \"requestId\": \"${requestId}\",\n" +
288 " \"callbackUrl\": \"${callbackUrl}\",\n" +
289 " \"sourceId\": \"so\",\n" +
290 " \"requestType\": \"${requestType}\"," +
291 " \"numSolutions\": 1,\n" +
292 " \"optimizers\": [\"placement\"],\n" +
293 " \"timeout\": 600\n" +
295 " \"placementInfo\": {\n" +
296 " \"requestParameters\": {\n" +
297 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
298 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
299 " \"customerName\": \"${customerLocation.customerName}\"\n" +
301 " \"subscriberInfo\": { \n" +
302 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
303 " \"subscriberName\": \"${subscriberName}\",\n" +
304 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
306 " \"placementDemands\": [\n" +
307 " ${placementDemands}\n" +
310 " \"serviceInfo\": {\n" +
311 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
312 " \"serviceName\": \"${serviceName}\",\n" +
313 " \"modelInfo\": {\n" +
314 " \"modelType\": \"${modelType}\",\n" +
315 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
316 " \"modelVersionId\": \"${modelVersionId}\",\n" +
317 " \"modelName\": \"${modelName}\",\n" +
318 " \"modelVersion\": \"${modelVersion}\",\n" +
319 " \"modelCustomizationName\": \"\"\n" +
325 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
327 } catch (Exception ex) {
328 utils.log("DEBUG", "buildRequest Exception: " + ex, isDebugEnabled)
333 * This method validates the callback response
334 * from OOF. If the response contains an
335 * exception the method will build and throw
336 * a workflow exception.
339 * @param response - the async callback response from oof
341 Void validateCallbackResponse(DelegateExecution execution, String response) {
342 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
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 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
353 "not contain placement solution.", isDebugEnabled)
354 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
355 "OOF Async Callback Response does not contain placement solution.")
357 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
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 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
378 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
381 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
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, isDebugEnabled)
522 String syncResponse = response.readEntity(String.class)
523 logDebug("CatalogDB response is: " + syncResponse, isDebugEnabled)
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()