a9bfbab32ab5d63c79a9f7c4f9d2dd5a62e4a812
[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(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY, false) as Boolean
196         val resolvedParamJsonContent =
197             ResourceAssignmentUtils.generateResourceDataForAssignments(resourceAssignments.toList())
198         val artifactTemplateDefinition = bluePrintRuntimeService.bluePrintContext().checkNodeTemplateArtifact(nodeTemplateName, artifactTemplate)
199
200         val resolvedContent = when {
201             artifactTemplateDefinition != null -> {
202                 blueprintTemplateService.generateContent(
203                         bluePrintRuntimeService, nodeTemplateName,
204                         artifactTemplate, resolvedParamJsonContent, false,
205                         mutableMapOf(
206                                 ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE to
207                                         properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
208                         )
209                 )
210             }
211             resolutionSummary -> {
212                 ResourceAssignmentUtils.generateResolutionSummaryData(resourceAssignments, resourceDefinitions)
213             }
214             else -> {
215                 resolvedParamJsonContent
216             }
217         }
218
219         if (isToStore(properties)) {
220             templateResolutionDBService.write(properties, resolvedContent, bluePrintRuntimeService, artifactPrefix)
221             log.info("Template resolution saved into database successfully : ($properties)")
222         }
223
224         return resolvedContent
225     }
226
227     override suspend fun resolveResourceDefinition(
228         blueprintRuntimeService: BluePrintRuntimeService<*>,
229         resourceDefinitions: MutableMap<String, ResourceDefinition>,
230         resolveDefinition: String,
231         sources: List<String>
232     ): MutableMap<String, JsonNode> {
233
234         // Populate Dummy Resource Assignments
235         val resourceAssignments = createResourceAssignments(resourceDefinitions, resolveDefinition, sources)
236
237         resolveResourceAssignments(
238             blueprintRuntimeService, resourceDefinitions, resourceAssignments,
239             UUID.randomUUID().toString(), hashMapOf()
240         )
241
242         // Get the data from Resource Assignments
243         return ResourceAssignmentUtils.generateResourceForAssignments(resourceAssignments)
244     }
245
246     /**
247      * Iterate the Batch, get the Resource Assignment, dictionary Name, Look for the Resource definition for the
248      * name, then get the type of the Resource Definition, Get the instance for the Resource Type and process the
249      * request.
250      */
251     override suspend fun resolveResourceAssignments(
252         blueprintRuntimeService: BluePrintRuntimeService<*>,
253         resourceDefinitions: MutableMap<String, ResourceDefinition>,
254         resourceAssignments: MutableList<ResourceAssignment>,
255         artifactPrefix: String,
256         properties: Map<String, Any>
257     ) {
258
259         val bulkSequenced = BulkResourceSequencingUtils.process(resourceAssignments)
260
261         // Check the BlueprintRuntime Service Should be ResourceAssignmentRuntimeService
262         val resourceAssignmentRuntimeService = if (blueprintRuntimeService !is ResourceAssignmentRuntimeService) {
263             ResourceAssignmentUtils.transformToRARuntimeService(blueprintRuntimeService, artifactPrefix)
264         } else {
265             blueprintRuntimeService
266         }
267
268         exposeOccurrencePropertyInResourceAssignments(resourceAssignmentRuntimeService, properties)
269
270         coroutineScope {
271             bulkSequenced.forEach { batchResourceAssignments ->
272                 // Execute Non Dependent Assignments in parallel ( ie asynchronously )
273                 val deferred = batchResourceAssignments
274                     .filter { it.name != "*" && it.name != "start" }
275                     .filter { it.status != BluePrintConstants.STATUS_SUCCESS }
276                     .map { resourceAssignment ->
277                         async {
278                             val dictionaryName = resourceAssignment.dictionaryName
279                             val dictionarySource = resourceAssignment.dictionarySource
280
281                             val processorName = processorName(dictionaryName!!, dictionarySource!!, resourceDefinitions)
282
283                             val resourceAssignmentProcessor =
284                                 applicationContext.getBean(processorName) as? ResourceAssignmentProcessor
285                                     ?: throw BluePrintProcessorException(
286                                         "failed to get resource processor ($processorName) " +
287                                                 "for resource assignment(${resourceAssignment.name})"
288                                     )
289                             try {
290                                 // Set BluePrint Runtime Service
291                                 resourceAssignmentProcessor.raRuntimeService = resourceAssignmentRuntimeService
292                                 // Set Resource Dictionaries
293                                 resourceAssignmentProcessor.resourceDictionaries = resourceDefinitions
294                                 // Invoke Apply Method
295                                 resourceAssignmentProcessor.applyNB(resourceAssignment)
296
297                                 if (isToStore(properties)) {
298                                     resourceResolutionDBService.write(
299                                         properties,
300                                         blueprintRuntimeService,
301                                         artifactPrefix,
302                                         resourceAssignment
303                                     )
304                                     log.info("Resource resolution saved into database successfully : (${resourceAssignment.name})")
305                                 }
306
307                                 // Set errors from RA
308                                 blueprintRuntimeService.setBluePrintError(resourceAssignmentRuntimeService.getBluePrintError())
309                             } catch (e: RuntimeException) {
310                                 log.error("Fail in processing ${resourceAssignment.name}", e)
311                                 throw BluePrintProcessorException(e)
312                             }
313                         }
314                     }
315                 log.debug("Resolving (${deferred.size})resources parallel.")
316                 deferred.awaitAll()
317             }
318         }
319     }
320
321     /**
322      * If the Source instance is "input", then it is not mandatory to have source Resource Definition, So it can
323      *  derive the default input processor.
324      */
325     private fun processorName(
326         dictionaryName: String,
327         dictionarySource: String,
328         resourceDefinitions: MutableMap<String, ResourceDefinition>
329     ): String {
330         val processorName: String = when (dictionarySource) {
331             "input" -> {
332                 "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-input"
333             }
334             "default" -> {
335                 "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-default"
336             }
337             else -> {
338                 val resourceDefinition = resourceDefinitions[dictionaryName]
339                     ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dictionaryName")
340
341                 val resourceSource = resourceDefinition.sources[dictionarySource]
342                     ?: throw BluePrintProcessorException("couldn't get resource definition $dictionaryName source($dictionarySource)")
343
344                 ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR.plus(resourceSource.type)
345             }
346         }
347         checkNotEmpty(processorName) {
348             "couldn't get processor name for resource dictionary definition($dictionaryName) source($dictionarySource)"
349         }
350
351         return processorName
352     }
353
354     // Check whether to store or not the resolution of resource and template
355     private fun isToStore(properties: Map<String, Any>): Boolean {
356         return properties.containsKey(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT) &&
357                 properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] as Boolean
358     }
359
360     // Check whether resolution already exist in the database for the specified resolution-key or resourceId/resourceType
361     private suspend fun isNewResolution(
362         bluePrintRuntimeService: BluePrintRuntimeService<*>,
363         properties: Map<String, Any>,
364         artifactPrefix: String
365     ): List<ResourceResolution> {
366         val occurrence = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] as Int
367         val resolutionKey = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] as String
368         val resourceId = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] as String
369         val resourceType = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] as String
370
371         if (resolutionKey.isNotEmpty()) {
372             val existingResourceAssignments =
373                 resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKeyAndOccurrence(
374                     bluePrintRuntimeService,
375                     resolutionKey,
376                     occurrence,
377                     artifactPrefix
378                 )
379             if (existingResourceAssignments.isNotEmpty()) {
380                 log.info(
381                     "Resolution with resolutionKey=($resolutionKey) already exist - will resolve all resources not already resolved.",
382                     resolutionKey
383                 )
384             }
385             return existingResourceAssignments
386         } else if (resourceId.isNotEmpty() && resourceType.isNotEmpty()) {
387             val existingResourceAssignments =
388                 resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResourceIdAndResourceTypeAndOccurrence(
389                     bluePrintRuntimeService,
390                     resourceId,
391                     resourceType,
392
393                     occurrence,
394                     artifactPrefix
395                 )
396             if (existingResourceAssignments.isNotEmpty()) {
397                 log.info(
398                     "Resolution with resourceId=($resourceId) and resourceType=($resourceType) already exist - will resolve " +
399                             "all resources not already resolved."
400                 )
401             }
402             return existingResourceAssignments
403         }
404         return emptyList()
405     }
406
407     // Update the resource assignment list with the status of the resource that have already been resolved
408     private fun updateResourceAssignmentWithExisting(
409         raRuntimeService: ResourceAssignmentRuntimeService,
410         resourceResolutionList: List<ResourceResolution>,
411         resourceAssignmentList: MutableList<ResourceAssignment>
412     ) {
413         resourceResolutionList.forEach { resourceResolution ->
414             if (resourceResolution.status == BluePrintConstants.STATUS_SUCCESS) {
415                 resourceAssignmentList.forEach {
416                     if (compareOne(resourceResolution, it)) {
417                         log.info(
418                             "Resource ({}) already resolved: value=({})", it.name,
419                             if (hasLogProtect(it.property)) LOG_REDACTED else resourceResolution.value
420                         )
421
422                         // Make sure to recreate value as per the defined type.
423                         val value = resourceResolution.value!!.asJsonType(it.property!!.type)
424                         it.property!!.value = value
425                         it.status = resourceResolution.status
426                         ResourceAssignmentUtils.setResourceDataValue(it, raRuntimeService, value)
427                     }
428                 }
429             }
430         }
431     }
432
433     // Comparision between what we have in the database vs what we have to assign.
434     private fun compareOne(resourceResolution: ResourceResolution, resourceAssignment: ResourceAssignment): Boolean {
435         return (resourceResolution.name == resourceAssignment.name &&
436                 resourceResolution.dictionaryName == resourceAssignment.dictionaryName &&
437                 resourceResolution.dictionarySource == resourceAssignment.dictionarySource &&
438                 resourceResolution.dictionaryVersion == resourceAssignment.version)
439     }
440
441     private fun exposeOccurrencePropertyInResourceAssignments(
442         raRuntimeService: ResourceAssignmentRuntimeService,
443         properties: Map<String, Any>
444     ) {
445         raRuntimeService.putResolutionStore(
446             ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE,
447             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
448         )
449     }
450 }