Containerization feature of SO
[so.git] / bpmn / MSOCommonBPMN / src / main / groovy / org / onap / so / bpmn / common / scripts / OofUtils.groovy
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.onap.so.bpmn.common.scripts
22
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
37
38 import java.lang.reflect.Array
39
40 import static org.onap.so.bpmn.common.scripts.GenericUtils.*
41
42 class OofUtils {
43         private static final MsoLogger msoLogger = MsoLogger.getMsoLogger(MsoLogger.Catalog.BPEL, OofUtils.class);
44     ExceptionUtil exceptionUtil = new ExceptionUtil()
45     JsonUtils jsonUtil = new JsonUtils()
46
47     private AbstractServiceTaskProcessor utils
48
49     public MsoUtils msoUtils = new MsoUtils()
50
51     public OofUtils(AbstractServiceTaskProcessor taskProcessor) {
52         this.utils = taskProcessor
53     }
54
55     /**
56      * This method builds the service-agnostic
57      * OOF json request to get a homing solution
58      * and license solution
59      *
60      * @param execution
61      * @param requestId
62      * @param decomposition - ServiceDecomposition object
63      * @param customerLocation -
64      * @param existingCandidates -
65      * @param excludedCandidates -
66      * @param requiredCandidates -
67      *
68      * @return request - OOF v1 payload - https://wiki.onap.org/pages/viewpage.action?pageId=25435066
69      */
70     String buildRequest(DelegateExecution execution,
71                         String requestId,
72                         ServiceDecomposition decomposition,
73                         Subscriber subscriber = null,
74                         Map customerLocation,
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 = ""
86
87         serviceInstanceId = execution.getVariable("serviceInstanceId")
88         serviceInstanceName = execution.getVariable("serviceInstanceName")
89
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")
94         }
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")
99         }
100         //Model Info
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()
107         //Subscriber Info
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()
115         }
116
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"
125             }
126         }
127
128         //Demands
129         String placementDemands = ""
130         StringBuilder sb = new StringBuilder()
131         List<AllottedResource> allottedResourceList = decomposition.getAllottedResources()
132         List<VnfResource> vnfResourceList = decomposition.getVnfResources()
133
134         if (allottedResourceList == null || allottedResourceList.isEmpty() ) {
135             utils.log("DEBUG", "Allotted Resources List is empty - will try to get service VNFs instead.",
136                     isDebugEnabled)
137             allottedResourceList = decomposition.getVnfResources()
138         }
139
140         if (allottedResourceList == null || allottedResourceList.isEmpty()) {
141             utils.log("DEBUG", "Resources List is Empty", isDebugEnabled)
142         } else {
143             for (AllottedResource resource : allottedResourceList) {
144                 utils.log("DEBUG", "Allotted Resource: " + resource.toString(),
145                         isDebugEnabled)
146                 def serviceResourceId = resource.getResourceId()
147                 def resourceModuleName = resource.getNfFunction()
148                 utils.log("DEBUG", "resourceModuleName: " + resourceModuleName,
149                         isDebugEnabled)
150                 def resourceModelInvariantId = "no-resourceModelInvariantId"
151                 def resourceModelVersionId = "no-resourceModelVersionId"
152
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
159                     }
160
161                 def resourceModelName = "" //Optional
162                 def resourceModelVersion = "" //Optional
163                 def resourceModelType = "" //Optional
164                 def tenantId = "" //Optional
165                 def requiredCandidatesJson = ""
166
167                 requiredCandidatesJson = createCandidateJson(
168                         existingCandidates,
169                         excludedCandidates,
170                         requiredCandidates)
171
172                 String demand =
173                         "      {\n" +
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" +
185                         "      },"
186
187                 placementDemands = sb.append(demand)
188             }
189             for (VnfResource vnfResource : vnfResourceList) {
190                 utils.log("DEBUG", "VNF Resource: " + vnfResource.toString(),
191                         isDebugEnabled)
192                 ModelInfo vnfResourceModelInfo = vnfResource.getModelInfo()
193                 def serviceResourceId = vnfResource.getResourceId()
194                 def resourceModuleName = vnfResource.getNfFunction()
195                 utils.log("DEBUG", "resourceModuleName: " + resourceModuleName,
196                         isDebugEnabled)
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 = ""
204
205
206                 String placementDemand =
207                         "      {\n" +
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" +
219                         "      },"
220
221                 placementDemands = sb.append(placementDemand)
222             }
223             placementDemands = placementDemands.substring(0, placementDemands.length() - 1)
224         }
225
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)
231         } else {
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()
242
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"]
249                 //}
250
251                     String licenseDemand =
252                         "{\n" +
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" +
263                         "  }\n"
264                         "},"
265
266                 licenseDemands = sb.append(licenseDemand)
267             }
268             licenseDemands = licenseDemands.substring(0, licenseDemands.length() - 1)
269         }*/
270
271         String request =
272                 "{\n" +
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" +
282                 "    },\n" +
283                 "  \"placementInfo\": {\n" +
284                 "    \"requestParameters\": {\n" +
285                 "      \"customerLatitude\": \"${customerLocation.customerLatitude}\",\n" +
286                 "      \"customerLongitude\": \"${customerLocation.customerLongitude}\",\n" +
287                 "      \"customerName\": \"${customerLocation.customerName}\"\n" +
288                 "    }," +
289                 "    \"subscriberInfo\": { \n" +
290                 "      \"globalSubscriberId\": \"${subscriberId}\",\n" +
291                 "      \"subscriberName\": \"${subscriberName}\",\n" +
292                 "      \"subscriberCommonSiteId\": \"${commonSiteId}\"\n" +
293                 "    },\n" +
294                 "    \"placementDemands\": [\n" +
295                 "      ${placementDemands}\n" +
296                 "      ]\n" +
297                 "    },\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" +
308                 "    }\n" +
309                 "  }\n" +
310                 "}"
311
312
313         utils.log("DEBUG", "Completed Building OOF Request", isDebugEnabled)
314         return request
315     }
316
317     /**
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.
322      *
323      * @param execution
324      * @param response - the async callback response from oof
325      */
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")
331         } else {
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.")
341                     } else {
342                         utils.log("DEBUG", "Error Occurred in Homing: " + statusMessage, isDebugEnabled)
343                         exceptionUtil.buildAndThrowWorkflowException(execution, 400, statusMessage)
344                     }
345                 } else {
346                     return
347                 }
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
359                 } else {
360                     errorMessage = "OOF Async Callback Response contains a Request Error. Unable to determine the Request Error Exception."
361                 }
362                 utils.log("DEBUG", "Error Occurred in Homing: " + errorMessage, isDebugEnabled)
363                 exceptionUtil.buildAndThrowWorkflowException(execution, 400, errorMessage)
364
365             } else {
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.")
368             }
369         }
370
371     }
372
373     /**
374      * This method creates candidates json for placement Demands.
375      *
376      * @param execution
377      * @param existingCandidates -
378      * @param excludedCandidates -
379      * @param requiredCandidates -
380      *
381      * @return candidatesJson - a JSON string with candidates
382      */
383     String createCandidateJson(ArrayList existingCandidates = null,
384                                ArrayList excludedCandidates = null,
385                                ArrayList requiredCandidates = null) {
386         def candidatesJson = ""
387         def type = ""
388         if (existingCandidates != null && existingCandidates != {}) {
389             sb = new StringBuilder()
390             sb.append(",\n" +
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" +
402                             "    },"
403                     sb.append(existingCandidateJson)
404                 }
405                 if (type == 'serviceInstanceId') {
406                     def serviceInstanceId = existingCandidate.get('identifiers')
407                     existingCandidateJson += "{\n" +
408                             "    \"identifierType\": \"serviceInstanceId\",\n" +
409                             "    \"identifiers\": [\"${serviceInstanceId}\"]\n" +
410                             "    },"
411                     sb.append(existingCandidateJson)
412                 }
413             }
414             if (existingCandidateJson != "") {
415                 sb.setLength(sb.length() - 1)
416                 candidatesJson = sb.append(",\n],")
417             }
418         }
419         if (excludedCandidates != null && excludedCandidates != {}) {
420             sb = new StringBuilder()
421             sb.append(",\n" +
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" +
433                             "    },"
434                     sb.append(excludedCandidateJson)
435                 }
436                 if (type == 'serviceInstanceId') {
437                     def serviceInstanceId = excludedCandidate.get('identifiers')
438                     excludedCandidateJson += "{\n" +
439                             "    \"identifierType\": \"serviceInstanceId\",\n" +
440                             "    \"identifiers\": [\"${serviceInstanceId}\"]\n" +
441                             "    },"
442                     sb.append(excludedCandidateJson)
443                 }
444             }
445             if (excludedCandidateJson != "") {
446                 sb.setLength(sb.length() - 1)
447                 candidatesJson = sb.append(",\n],")
448             }
449         }
450         if (requiredCandidates != null && requiredCandidates != {}) {
451             sb = new StringBuilder()
452             sb.append(",\n" +
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" +
464                             "    },"
465                     sb.append(requiredCandidatesJson)
466                 }
467                 if (type == 'serviceInstanceId') {
468                     def serviceInstanceId = requiredCandidate.get('identifiers')
469                     requiredCandidatesJson += "{\n" +
470                             "    \"identifierType\": \"serviceInstanceId\",\n" +
471                             "    \"identifiers\": [\"${serviceInstanceId}\"]\n" +
472                             "    },"
473                     sb.append(requiredCandidatesJson)
474                 }
475             }
476             if (requiredCandidatesJson != "") {
477                 sb.setLength(sb.length() - 1)
478                 candidatesJson = sb.append(",\n],")
479             }
480         }
481         if (candidatesJson != "") {candidatesJson = candidatesJson.substring(0, candidatesJson.length() - 1)}
482         return candidatesJson
483     }
484 }