4ad86b4add17a4e186e7e432785608de76325fee
[ccsdk/cds.git] /
1 /*
2  *  Copyright © 2017-2018 AT&T Intellectual Property.
3  *  Modifications Copyright © 2018-2019 IBM, Bell Canada
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17
18 package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution
19
20 import com.fasterxml.jackson.databind.JsonNode
21 import kotlinx.coroutines.async
22 import kotlinx.coroutines.awaitAll
23 import kotlinx.coroutines.coroutineScope
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants.OUTPUT_ASSIGNMENT_MAP
25 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.ResourceResolution
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.ResourceResolutionDBService
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.TemplateResolutionService
28 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.processor.ResourceAssignmentProcessor
29 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceAssignmentUtils
30 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceDefinitionUtils.createResourceAssignments
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
32 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
33 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
34 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonType
35 import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty
36 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants.LOG_REDACTED
37 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
38 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintTemplateService
39 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
40 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils.Companion.hasLogProtect
41 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
42 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition
43 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.utils.BulkResourceSequencingUtils
44 import org.slf4j.LoggerFactory
45 import org.springframework.context.ApplicationContext
46 import org.springframework.stereotype.Service
47 import java.util.UUID
48
49 interface ResourceResolutionService {
50
51     fun registeredResourceSources(): List<String>
52
53     suspend fun resolveFromDatabase(
54         bluePrintRuntimeService: BluePrintRuntimeService<*>,
55         artifactTemplate: String,
56         resolutionKey: String
57     ): String
58
59     suspend fun resolveResources(
60         bluePrintRuntimeService: BluePrintRuntimeService<*>,
61         nodeTemplateName: String,
62         artifactNames: List<String>,
63         properties: Map<String, Any>
64     ): MutableMap<String, String>
65
66     suspend fun resolveResources(
67         bluePrintRuntimeService: BluePrintRuntimeService<*>,
68         nodeTemplateName: String,
69         artifactPrefix: String,
70         properties: Map<String, Any>
71     ): String
72
73     /** Resolve resources for all the sources defined in a particular resource Definition[resolveDefinition]
74      * with other [resourceDefinitions] dependencies for the sources [sources]
75      * Used to get the same resource values from multiple sources. **/
76     suspend fun resolveResourceDefinition(
77         blueprintRuntimeService: BluePrintRuntimeService<*>,
78         resourceDefinitions: MutableMap<String, ResourceDefinition>,
79         resolveDefinition: String,
80         sources: List<String>
81     ):
82         MutableMap<String, JsonNode>
83
84     suspend fun resolveResourceAssignments(
85         blueprintRuntimeService: BluePrintRuntimeService<*>,
86         resourceDefinitions: MutableMap<String, ResourceDefinition>,
87         resourceAssignments: MutableList<ResourceAssignment>,
88         artifactPrefix: String,
89         properties: Map<String, Any>
90     )
91 }
92
93 @Service(ResourceResolutionConstants.SERVICE_RESOURCE_RESOLUTION)
94 open class ResourceResolutionServiceImpl(
95     private var applicationContext: ApplicationContext,
96     private var templateResolutionDBService: TemplateResolutionService,
97     private var blueprintTemplateService: BluePrintTemplateService,
98     private var resourceResolutionDBService: ResourceResolutionDBService
99 ) :
100     ResourceResolutionService {
101
102     private val log = LoggerFactory.getLogger(ResourceResolutionService::class.java)
103
104     override fun registeredResourceSources(): List<String> {
105         return applicationContext.getBeanNamesForType(ResourceAssignmentProcessor::class.java)
106             .filter { it.startsWith(ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR) }
107             .map { it.substringAfter(ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR) }
108     }
109
110     override suspend fun resolveFromDatabase(
111         bluePrintRuntimeService: BluePrintRuntimeService<*>,
112         artifactTemplate: String,
113         resolutionKey: String
114     ): String {
115         return templateResolutionDBService.findByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
116             bluePrintRuntimeService,
117             artifactTemplate,
118             resolutionKey
119         )
120     }
121
122     override suspend fun resolveResources(
123         bluePrintRuntimeService: BluePrintRuntimeService<*>,
124         nodeTemplateName: String,
125         artifactNames: List<String>,
126         properties: Map<String, Any>
127     ): MutableMap<String, String> {
128
129         val resourceAssignmentRuntimeService =
130             ResourceAssignmentUtils.transformToRARuntimeService(bluePrintRuntimeService, artifactNames.toString())
131
132         val resolvedParams: MutableMap<String, String> = hashMapOf()
133         artifactNames.forEach { artifactName ->
134             val resolvedContent = resolveResources(
135                 resourceAssignmentRuntimeService, nodeTemplateName,
136                 artifactName, properties
137             )
138
139             resolvedParams[artifactName] = resolvedContent
140         }
141         return resolvedParams
142     }
143
144     override suspend fun resolveResources(
145         bluePrintRuntimeService: BluePrintRuntimeService<*>,
146         nodeTemplateName: String,
147         artifactPrefix: String,
148         properties: Map<String, Any>
149     ): String {
150
151         // Template Artifact Definition Name
152         val artifactTemplate = "$artifactPrefix-template"
153         // Resource Assignment Artifact Definition Name
154         val artifactMapping = "$artifactPrefix-mapping"
155
156         log.info("Resolving resource with resource assignment artifact($artifactMapping)")
157
158         val resourceAssignmentContent =
159             bluePrintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, artifactMapping)
160
161         val resourceAssignments: MutableList<ResourceAssignment> =
162             JacksonUtils.getListFromJson(resourceAssignmentContent, ResourceAssignment::class.java)
163                 as? MutableList<ResourceAssignment>
164                 ?: throw BluePrintProcessorException("couldn't get Dictionary Definitions")
165
166         if (isToStore(properties)) {
167             val existingResourceResolution = isNewResolution(bluePrintRuntimeService, properties, artifactPrefix)
168             if (existingResourceResolution.isNotEmpty()) {
169                 updateResourceAssignmentWithExisting(
170                     bluePrintRuntimeService as ResourceAssignmentRuntimeService,
171                     existingResourceResolution, resourceAssignments
172                 )
173             }
174         }
175
176         // Get the Resource Dictionary Name
177         val resourceDefinitions: MutableMap<String, ResourceDefinition> = ResourceAssignmentUtils
178             .resourceDefinitions(bluePrintRuntimeService.bluePrintContext().rootPath)
179
180         // Resolve resources
181         resolveResourceAssignments(
182             bluePrintRuntimeService,
183             resourceDefinitions,
184             resourceAssignments,
185             artifactPrefix,
186             properties
187         )
188
189         bluePrintRuntimeService.setNodeTemplateAttributeValue(
190                 nodeTemplateName,
191                 OUTPUT_ASSIGNMENT_MAP,
192                 ResourceAssignmentUtils.generateAssignmentMap(artifactPrefix, resourceAssignments)
193         )
194
195         val resolutionSummary = properties.getOrDefault(
196             ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY,
197             false
198         ) as Boolean
199         val resolvedParamJsonContent =
200             ResourceAssignmentUtils.generateResourceDataForAssignments(resourceAssignments.toList())
201         val artifactTemplateDefinition =
202             bluePrintRuntimeService.bluePrintContext().checkNodeTemplateArtifact(nodeTemplateName, artifactTemplate)
203
204         val resolvedContent = when {
205             artifactTemplateDefinition != null -> {
206                 blueprintTemplateService.generateContent(
207                     bluePrintRuntimeService, nodeTemplateName,
208                     artifactTemplate, resolvedParamJsonContent, false,
209                     mutableMapOf(
210                         ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE to
211                             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE]
212                                 .asJsonPrimitive()
213                         )
214                 )
215             }
216             resolutionSummary -> {
217                 ResourceAssignmentUtils.generateResolutionSummaryData(resourceAssignments, resourceDefinitions)
218             }
219             else -> {
220                 resolvedParamJsonContent
221             }
222         }
223
224         if (isToStore(properties)) {
225             templateResolutionDBService.write(properties, resolvedContent, bluePrintRuntimeService, artifactPrefix)
226             log.info("Template resolution saved into database successfully : ($properties)")
227         }
228
229         return resolvedContent
230     }
231
232     override suspend fun resolveResourceDefinition(
233         blueprintRuntimeService: BluePrintRuntimeService<*>,
234         resourceDefinitions: MutableMap<String, ResourceDefinition>,
235         resolveDefinition: String,
236         sources: List<String>
237     ): MutableMap<String, JsonNode> {
238
239         // Populate Dummy Resource Assignments
240         val resourceAssignments = createResourceAssignments(resourceDefinitions, resolveDefinition, sources)
241
242         resolveResourceAssignments(
243             blueprintRuntimeService, resourceDefinitions, resourceAssignments,
244             UUID.randomUUID().toString(), hashMapOf()
245         )
246
247         // Get the data from Resource Assignments
248         return ResourceAssignmentUtils.generateResourceForAssignments(resourceAssignments)
249     }
250
251     /**
252      * Iterate the Batch, get the Resource Assignment, dictionary Name, Look for the Resource definition for the
253      * name, then get the type of the Resource Definition, Get the instance for the Resource Type and process the
254      * request.
255      */
256     override suspend fun resolveResourceAssignments(
257         blueprintRuntimeService: BluePrintRuntimeService<*>,
258         resourceDefinitions: MutableMap<String, ResourceDefinition>,
259         resourceAssignments: MutableList<ResourceAssignment>,
260         artifactPrefix: String,
261         properties: Map<String, Any>
262     ) {
263
264         val bulkSequenced = BulkResourceSequencingUtils.process(resourceAssignments)
265
266         // Check the BlueprintRuntime Service Should be ResourceAssignmentRuntimeService
267         val resourceAssignmentRuntimeService = if (blueprintRuntimeService !is ResourceAssignmentRuntimeService) {
268             ResourceAssignmentUtils.transformToRARuntimeService(blueprintRuntimeService, artifactPrefix)
269         } else {
270             blueprintRuntimeService
271         }
272
273         exposeOccurrencePropertyInResourceAssignments(resourceAssignmentRuntimeService, properties)
274
275         coroutineScope {
276             bulkSequenced.forEach { batchResourceAssignments ->
277                 // Execute Non Dependent Assignments in parallel ( ie asynchronously )
278                 val deferred = batchResourceAssignments
279                     .filter { it.name != "*" && it.name != "start" }
280                     .filter { it.status != BluePrintConstants.STATUS_SUCCESS }
281                     .map { resourceAssignment ->
282                         async {
283                             val dictionaryName = resourceAssignment.dictionaryName
284                             val dictionarySource = resourceAssignment.dictionarySource
285
286                             val processorName = processorName(dictionaryName!!, dictionarySource!!, resourceDefinitions)
287
288                             val resourceAssignmentProcessor =
289                                 applicationContext.getBean(processorName) as? ResourceAssignmentProcessor
290                                     ?: throw BluePrintProcessorException(
291                                         "failed to get resource processor ($processorName) " +
292                                             "for resource assignment(${resourceAssignment.name})"
293                                     )
294                             try {
295                                 // Set BluePrint Runtime Service
296                                 resourceAssignmentProcessor.raRuntimeService = resourceAssignmentRuntimeService
297                                 // Set Resource Dictionaries
298                                 resourceAssignmentProcessor.resourceDictionaries = resourceDefinitions
299
300                                 resourceAssignmentProcessor.resourceAssignments = resourceAssignments
301
302                                 // Invoke Apply Method
303                                 resourceAssignmentProcessor.applyNB(resourceAssignment)
304
305                                 if (isToStore(properties)) {
306                                     resourceResolutionDBService.write(
307                                         properties,
308                                         blueprintRuntimeService,
309                                         artifactPrefix,
310                                         resourceAssignment
311                                     )
312                                     log.info("Resource resolution saved into database successfully : (${resourceAssignment.name})")
313                                 }
314
315                                 // Set errors from RA
316                                 blueprintRuntimeService.setBluePrintError(resourceAssignmentRuntimeService.getBluePrintError())
317                             } catch (e: RuntimeException) {
318                                 log.error("Fail in processing ${resourceAssignment.name}", e)
319                                 throw BluePrintProcessorException(e)
320                             }
321                         }
322                     }
323                 log.debug("Resolving (${deferred.size})resources parallel.")
324                 deferred.awaitAll()
325             }
326         }
327     }
328
329     /**
330      * If the Source instance is "input", then it is not mandatory to have source Resource Definition, So it can
331      *  derive the default input processor.
332      */
333     private fun processorName(
334         dictionaryName: String,
335         dictionarySource: String,
336         resourceDefinitions: MutableMap<String, ResourceDefinition>
337     ): String {
338         val processorName: String = when (dictionarySource) {
339             "input" -> {
340                 "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-input"
341             }
342             "default" -> {
343                 "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-default"
344             }
345             else -> {
346                 val resourceDefinition = resourceDefinitions[dictionaryName]
347                     ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dictionaryName")
348
349                 val resourceSource = resourceDefinition.sources[dictionarySource]
350                     ?: throw BluePrintProcessorException("couldn't get resource definition $dictionaryName source($dictionarySource)")
351
352                 ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR.plus(resourceSource.type)
353             }
354         }
355         checkNotEmpty(processorName) {
356             "couldn't get processor name for resource dictionary definition($dictionaryName) source($dictionarySource)"
357         }
358
359         return processorName
360     }
361
362     // Check whether to store or not the resolution of resource and template
363     private fun isToStore(properties: Map<String, Any>): Boolean {
364         return properties.containsKey(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT) &&
365             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] as Boolean
366     }
367
368     // Check whether resolution already exist in the database for the specified resolution-key or resourceId/resourceType
369     private suspend fun isNewResolution(
370         bluePrintRuntimeService: BluePrintRuntimeService<*>,
371         properties: Map<String, Any>,
372         artifactPrefix: String
373     ): List<ResourceResolution> {
374         val occurrence = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] as Int
375         val resolutionKey = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] as String
376         val resourceId = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] as String
377         val resourceType = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] as String
378
379         if (resolutionKey.isNotEmpty()) {
380             val existingResourceAssignments =
381                 resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKeyAndOccurrence(
382                     bluePrintRuntimeService,
383                     resolutionKey,
384                     occurrence,
385                     artifactPrefix
386                 )
387             if (existingResourceAssignments.isNotEmpty()) {
388                 log.info(
389                     "Resolution with resolutionKey=($resolutionKey) already exist - will resolve all resources not already resolved.",
390                     resolutionKey
391                 )
392             }
393             return existingResourceAssignments
394         } else if (resourceId.isNotEmpty() && resourceType.isNotEmpty()) {
395             val existingResourceAssignments =
396                 resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResourceIdAndResourceTypeAndOccurrence(
397                     bluePrintRuntimeService,
398                     resourceId,
399                     resourceType,
400
401                     occurrence,
402                     artifactPrefix
403                 )
404             if (existingResourceAssignments.isNotEmpty()) {
405                 log.info(
406                     "Resolution with resourceId=($resourceId) and resourceType=($resourceType) already " +
407                         "exist - will resolve all resources not already resolved."
408                 )
409             }
410             return existingResourceAssignments
411         }
412         return emptyList()
413     }
414
415     // Update the resource assignment list with the status of the resource that have already been resolved
416     private fun updateResourceAssignmentWithExisting(
417         raRuntimeService: ResourceAssignmentRuntimeService,
418         resourceResolutionList: List<ResourceResolution>,
419         resourceAssignmentList: MutableList<ResourceAssignment>
420     ) {
421         resourceResolutionList.forEach { resourceResolution ->
422             if (resourceResolution.status == BluePrintConstants.STATUS_SUCCESS) {
423                 resourceAssignmentList.forEach {
424                     if (compareOne(resourceResolution, it)) {
425                         log.info(
426                             "Resource ({}) already resolved: value=({})", it.name,
427                             if (hasLogProtect(it.property)) LOG_REDACTED else resourceResolution.value
428                         )
429
430                         // Make sure to recreate value as per the defined type.
431                         val value = resourceResolution.value!!.asJsonType(it.property!!.type)
432                         it.property!!.value = value
433                         it.status = resourceResolution.status
434                         ResourceAssignmentUtils.setResourceDataValue(it, raRuntimeService, value)
435                     }
436                 }
437             }
438         }
439     }
440
441     // Comparision between what we have in the database vs what we have to assign.
442     private fun compareOne(resourceResolution: ResourceResolution, resourceAssignment: ResourceAssignment): Boolean {
443         return (resourceResolution.name == resourceAssignment.name &&
444             resourceResolution.dictionaryName == resourceAssignment.dictionaryName &&
445             resourceResolution.dictionarySource == resourceAssignment.dictionarySource &&
446             resourceResolution.dictionaryVersion == resourceAssignment.version)
447     }
448
449     private fun exposeOccurrencePropertyInResourceAssignments(
450         raRuntimeService: ResourceAssignmentRuntimeService,
451         properties: Map<String, Any>
452     ) {
453         raRuntimeService.putResolutionStore(
454             ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE,
455             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
456         )
457     }
458 }