2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 - 2018 AT&T Intellectual Property. 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.common.scripts.MsoUtils
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.logger.MsoLogger
38 import java.lang.reflect.Array
40 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
43 private static final MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL, OofUtils.class);
44 ExceptionUtil exceptionUtil = new ExceptionUtil()
45 JsonUtils jsonUtil = new JsonUtils()
47 private AbstractServiceTaskProcessor utils
49 public MsoUtils msoUtils = new MsoUtils()
51 public OofUtils(AbstractServiceTaskProcessor taskProcessor) {
52 this.utils = taskProcessor
56 * This method builds the service-agnostic
57 * OOF json request to get a homing solution
58 * and license solution
62 * @param decomposition - ServiceDecomposition object
63 * @param customerLocation -
64 * @param existingCandidates -
65 * @param excludedCandidates -
66 * @param requiredCandidates -
68 * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
70 String buildRequest(DelegateExecution execution,
72 ServiceDecomposition decomposition,
73 Subscriber subscriber = null,
75 ArrayList existingCandidates = null,
76 ArrayList excludedCandidates = null,
77 ArrayList requiredCandidates = null) {
78 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
79 utils.log("DEBUG", "Started Building OOF Request", isDebugEnabled)
80 def callbackUrl = utils.createWorkflowMessageAdapterCallbackURL(execution, "oofResponse", requestId)
81 def transactionId = requestId
82 //ServiceInstance Info
83 ServiceInstance serviceInstance = decomposition.getServiceInstance()
84 def serviceInstanceId = ""
85 def serviceInstanceName = ""
87 serviceInstanceId = execution.getVariable("serviceInstanceId")
88 serviceInstanceName = execution.getVariable("serviceInstanceName")
90 if (serviceInstanceId == null || serviceInstanceId == "null") {
91 utils.log("DEBUG", "Unable to obtain Service Instance Id", isDebugEnabled)
92 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
93 "obtain Service Instance Id, execution.getVariable(\"serviceInstanceName\") is null")
95 if (serviceInstanceName == null || serviceInstanceName == "null") {
96 utils.log("DEBUG", "Unable to obtain Service Instance Name", isDebugEnabled)
97 exceptionUtil.buildAndThrowWorkflowException(execution, 400, "Internal Error - Unable to " +
98 "obtain Service Instance Name, execution.getVariable(\"serviceInstanceName\") is null")
101 ModelInfo model = decomposition.getModelInfo()
102 String modelType = model.getModelType()
103 String modelInvariantId = model.getModelInvariantUuid()
104 String modelVersionId = model.getModelUuid()
105 String modelName = model.getModelName()
106 String modelVersion = model.getModelVersion()
108 String subscriberId = ""
109 String subscriberName = ""
110 String commonSiteId = ""
111 if (subscriber != null){
112 subscriberId = subscriber.getGlobalId()
113 subscriberName = subscriber.getName()
114 commonSiteId = subscriber.getCommonSiteId()
117 //Determine RequestType
118 //TODO Figure out better way to determine this
119 String requestType = "create"
120 List<Resource> resources = decomposition.getServiceResources()
121 for(Resource r:resources){
122 HomingSolution currentSolution = (HomingSolution) r.getCurrentHomingSolution()
123 if(currentSolution != null){
124 requestType = "speed changed"
129 String placementDemands = ""
130 StringBuilder sb = new StringBuilder()
131 List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
132 List<VnfResource> vnfResourceList = decomposition.getVnfResources()
134 if (allottedResourceList == null || allottedResourceList.isEmpty() ) {
135 utils.log("DEBUG", "Allotted Resources List is empty - will try to get service VNFs instead.",
137 allottedResourceList = decomposition.getVnfResources()
140 if (allottedResourceList == null || allottedResourceList.isEmpty()) {
141 utils.log("DEBUG", "Resources List is Empty", isDebugEnabled)
143 for (AllottedResource resource : allottedResourceList) {
144 utils.log("DEBUG", "Allotted Resource: " + resource.toString(),
146 def serviceResourceId = resource.getResourceId()
147 def resourceModuleName = resource.getNfFunction()
148 utils.log("DEBUG", "resourceModuleName: " + resourceModuleName,
150 def resourceModelInvariantId = "no-resourceModelInvariantId"
151 def resourceModelVersionId = "no-resourceModelVersionId"
153 List modelIdLst = execution.getVariable("homingModelIds")
154 utils.log("DEBUG", "Incoming modelIdLst is: " + modelIdLst.toString(), isDebugEnabled)
155 for (Map modelId : modelIdLst )
156 if (resourceModuleName == modelId.resourceModuleName) {
157 resourceModelInvariantId = modelId.resourceModelInvariantId
158 resourceModelVersionId = modelId.resourceModelVersionId
161 def resourceModelName = "" //Optional
162 def resourceModelVersion = "" //Optional
163 def resourceModelType = "" //Optional
164 def tenantId = "" //Optional
165 def requiredCandidatesJson = ""
167 requiredCandidatesJson = createCandidateJson(
174 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
175 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
176 " \"tenantId\": \"${tenantId}\",\n" +
177 " \"resourceModelInfo\": {\n" +
178 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
179 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
180 " \"modelName\": \"${resourceModelName}\",\n" +
181 " \"modelType\": \"${resourceModelType}\",\n" +
182 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
183 " \"modelCustomizationName\": \"\"\n" +
184 " }" + requiredCandidatesJson + "\n" +
187 placementDemands = sb.append(demand)
189 for (VnfResource vnfResource : vnfResourceList) {
190 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
192 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
193 def serviceResourceId = vnfResource.getResourceId()
194 def resourceModuleName = vnfResource.getNfFunction()
195 utils.log("DEBUG", "resourceModuleName: " + resourceModuleName,
197 def resourceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
198 def resourceModelName = vnfResourceModelInfo.getModelName()
199 def resourceModelVersion = vnfResourceModelInfo.getModelVersion()
200 def resourceModelVersionId = vnfResourceModelInfo.getModelUuid()
201 def resourceModelType = vnfResourceModelInfo.getModelType()
202 def tenantId = "" //Optional
203 def requiredCandidatesJson = ""
206 String placementDemand =
208 " \"resourceModuleName\": \"${resourceModuleName}\",\n" +
209 " \"serviceResourceId\": \"${serviceResourceId}\",\n" +
210 " \"tenantId\": \"${tenantId}\",\n" +
211 " \"resourceModelInfo\": {\n" +
212 " \"modelInvariantId\": \"${resourceModelInvariantId}\",\n" +
213 " \"modelVersionId\": \"${resourceModelVersionId}\",\n" +
214 " \"modelName\": \"${resourceModelName}\",\n" +
215 " \"modelType\": \"${resourceModelType}\",\n" +
216 " \"modelVersion\": \"${resourceModelVersion}\",\n" +
217 " \"modelCustomizationName\": \"\"\n" +
218 " }" + requiredCandidatesJson + "\n" +
221 placementDemands = sb.append(placementDemand)
223 placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
226 /* Commenting Out Licensing as OOF doesn't support for Beijing
227 String licenseDemands = ""
228 sb = new StringBuilder()
229 if (vnfResourceList.isEmpty() || vnfResourceList == null) {
230 utils.log("DEBUG", "Vnf Resources List is Empty", isDebugEnabled)
232 for (VnfResource vnfResource : vnfResourceList) {
233 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
234 def resourceInstanceType = vnfResource.getResourceType()
235 def serviceResourceId = vnfResource.getResourceId()
236 def resourceModuleName = vnfResource.getResourceType()
237 def resouceModelInvariantId = vnfResourceModelInfo.getModelInvariantUuid()
238 def resouceModelName = vnfResourceModelInfo.getModelName()
239 def resouceModelVersion = vnfResourceModelInfo.getModelVersion()
240 def resouceModelVersionId = vnfResourceModelInfo.getModelUuid()
241 def resouceModelType = vnfResourceModelInfo.getModelType()
243 // TODO Add Existing Licenses to demand
244 //"existingLicenses": {
245 //"entitlementPoolUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
246 // "43257b49-9602-4fe5-9337-094e52bc9435"],
247 //"licenseKeyGroupUUID": ["87257b49-9602-4ca1-9817-094e52bc873b",
248 // "43257b49-9602-4fe5-9337-094e52bc9435"]
251 String licenseDemand =
253 "\"resourceModuleName\": \"${resourceModuleName}\",\n" +
254 "\"serviceResourceId\": \"${serviceResourceId}\",\n" +
255 "\"resourceInstanceType\": \"${resourceInstanceType}\",\n" +
256 "\"resourceModelInfo\": {\n" +
257 " \"modelInvariantId\": \"${resouceModelInvariantId}\",\n" +
258 " \"modelVersionId\": \"${resouceModelVersionId}\",\n" +
259 " \"modelName\": \"${resouceModelName}\",\n" +
260 " \"modelType\": \"${resouceModelType}\",\n" +
261 " \"modelVersion\": \"${resouceModelVersion}\",\n" +
262 " \"modelCustomizationName\": \"\"\n" +
266 licenseDemands = sb.append(licenseDemand)
268 licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
273 " \"requestInfo\": {\n" +
274 " \"transactionId\": \"${transactionId}\",\n" +
275 " \"requestId\": \"${requestId}\",\n" +
276 " \"callbackUrl\": \"${callbackUrl}\",\n" +
277 " \"sourceId\": \"so\",\n" +
278 " \"requestType\": \"${requestType}\"," +
279 " \"numSolutions\": 1,\n" +
280 " \"optimizers\": [\"placement\"],\n" +
281 " \"timeout\": 600\n" +
283 " \"placementInfo\": {\n" +
284 " \"requestParameters\": {\n" +
285 " \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
286 " \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
287 " \"customerName\": \"${customerLocation.customerName}\"\n" +
289 " \"subscriberInfo\": { \n" +
290 " \"globalSubscriberId\": \"${subscriberId}\",\n" +
291 " \"subscriberName\": \"${subscriberName}\",\n" +
292 " \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
294 " \"placementDemands\": [\n" +
295 " ${placementDemands}\n" +
298 " \"serviceInfo\": {\n" +
299 " \"serviceInstanceId\": \"${serviceInstanceId}\",\n" +
300 " \"serviceName\": \"${serviceInstanceName}\",\n" +
301 " \"modelInfo\": {\n" +
302 " \"modelType\": \"${modelType}\",\n" +
303 " \"modelInvariantId\": \"${modelInvariantId}\",\n" +
304 " \"modelVersionId\": \"${modelVersionId}\",\n" +
305 " \"modelName\": \"${modelName}\",\n" +
306 " \"modelVersion\": \"${modelVersion}\",\n" +
307 " \"modelCustomizationName\": \"\"\n" +
313 utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
318 * This method validates the callback response
319 * from OOF. If the response contains an
320 * exception the method will build and throw
321 * a workflow exception.
324 * @param response - the async callback response from oof
326 Void validateCallbackResponse(DelegateExecution execution, String response) {
327 def isDebugEnabled = execution.getVariable("isDebugLogEnabled")
328 String placements = ""
329 if (isBlank(response)) {
330 exceptionUtil.buildAndThrowWorkflowException(execution, 5000, "OOF Async Callback Response is Empty")
332 if (JsonUtils.jsonElementExist(response, "solutions.placementSolutions")) {
333 placements = jsonUtil.getJsonValue(response, "solutions.placementSolutions")
334 if (isBlank(placements) || placements.equalsIgnoreCase("[]")) {
335 String statusMessage = jsonUtil.getJsonValue(response, "statusMessage")
336 if (isBlank(statusMessage)) {
337 utils.log("DEBUG", "Error Occurred in Homing: OOF Async Callback Response does " +
338 "not contain placement solution.", isDebugEnabled)
339 exceptionUtil.buildAndThrowWorkflowException(execution, 400,
340 "OOF Async Callback Response does not contain placement solution.")
342 utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
343 exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
348 } else if (response.contains("error") || response.contains("Error") ) {
349 String errorMessage = ""
350 if (response.contains("policyException")) {
351 String text = jsonUtil.getJsonValue(response, "requestError.policyException.text")
352 errorMessage = "OOF Async Callback Response contains a Request Error Policy Exception: " + text
353 } else if (response.contains("Unable to find any candidate for demand")) {
354 errorMessage = "OOF Async Callback Response contains error: Unable to find any candidate for " +
355 "demand *** Response: " + response.toString()
356 } else if (response.contains("serviceException")) {
357 String text = jsonUtil.getJsonValue(response, "requestError.serviceException.text")
358 errorMessage = "OOF Async Callback Response contains a Request Error Service Exception: " + text
360 errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
362 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
363 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
366 utils.log("DEBUG", "Error Occurred in Homing: Received an Unknown Async Callback Response from OOF.", isDebugEnabled)
367 exceptionUtil.buildAndThrowWorkflowException(execution, 2500, "Received an Unknown Async Callback Response from OOF.")
374 * This method creates candidates json for placement Demands.
377 * @param existingCandidates -
378 * @param excludedCandidates -
379 * @param requiredCandidates -
381 * @return candidatesJson - a JSON string with candidates
383 String createCandidateJson(ArrayList existingCandidates = null,
384 ArrayList excludedCandidates = null,
385 ArrayList requiredCandidates = null) {
386 def candidatesJson = ""
388 if (existingCandidates != null && existingCandidates != {}) {
389 sb = new StringBuilder()
391 " \"existingCandidates\": [\n")
392 def existingCandidateJson = ""
393 existingCandidates.each { existingCandidate ->
394 type = existingCandidate.get('identifierType')
395 if (type == 'vimId') {
396 def cloudOwner = existingCandidate.get('cloudOwner')
397 def cloudRegionId = existingCandidate.get('identifiers')
398 existingCandidateJson = "{\n" +
399 " \"identifierType\": \"vimId\",\n" +
400 " \"cloudOwner\": \"${cloudOwner}\",\n" +
401 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
403 sb.append(existingCandidateJson)
405 if (type == 'serviceInstanceId') {
406 def serviceInstanceId = existingCandidate.get('identifiers')
407 existingCandidateJson += "{\n" +
408 " \"identifierType\": \"serviceInstanceId\",\n" +
409 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
411 sb.append(existingCandidateJson)
414 if (existingCandidateJson != "") {
415 sb.setLength(sb.length() - 1)
416 candidatesJson = sb.append(",\n],")
419 if (excludedCandidates != null && excludedCandidates != {}) {
420 sb = new StringBuilder()
422 " \"excludedCandidates\": [\n")
423 def excludedCandidateJson = ""
424 excludedCandidates.each { excludedCandidate ->
425 type = excludedCandidate.get('identifierType')
426 if (type == 'vimId') {
427 def cloudOwner = excludedCandidate.get('cloudOwner')
428 def cloudRegionId = excludedCandidate.get('identifiers')
429 excludedCandidateJson = "{\n" +
430 " \"identifierType\": \"vimId\",\n" +
431 " \"cloudOwner\": \"${cloudOwner}\",\n" +
432 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
434 sb.append(excludedCandidateJson)
436 if (type == 'serviceInstanceId') {
437 def serviceInstanceId = excludedCandidate.get('identifiers')
438 excludedCandidateJson += "{\n" +
439 " \"identifierType\": \"serviceInstanceId\",\n" +
440 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
442 sb.append(excludedCandidateJson)
445 if (excludedCandidateJson != "") {
446 sb.setLength(sb.length() - 1)
447 candidatesJson = sb.append(",\n],")
450 if (requiredCandidates != null && requiredCandidates != {}) {
451 sb = new StringBuilder()
453 " \"requiredCandidates\": [\n")
454 def requiredCandidatesJson = ""
455 requiredCandidates.each { requiredCandidate ->
456 type = requiredCandidate.get('identifierType')
457 if (type == 'vimId') {
458 def cloudOwner = requiredCandidate.get('cloudOwner')
459 def cloudRegionId = requiredCandidate.get('identifiers')
460 requiredCandidatesJson = "{\n" +
461 " \"identifierType\": \"vimId\",\n" +
462 " \"cloudOwner\": \"${cloudOwner}\",\n" +
463 " \"identifiers\": [\"${cloudRegionId}\"]\n" +
465 sb.append(requiredCandidatesJson)
467 if (type == 'serviceInstanceId') {
468 def serviceInstanceId = requiredCandidate.get('identifiers')
469 requiredCandidatesJson += "{\n" +
470 " \"identifierType\": \"serviceInstanceId\",\n" +
471 " \"identifiers\": [\"${serviceInstanceId}\"]\n" +
473 sb.append(requiredCandidatesJson)
476 if (requiredCandidatesJson != "") {
477 sb.setLength(sb.length() - 1)
478 candidatesJson = sb.append(",\n],")
481 if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
482 return candidatesJson