2352c7dbee8130b8053bc04c633e8c615e9ca088
[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.db.ResourceResolution
25 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.ResourceResolutionDBService
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.TemplateResolutionService
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.processor.ResourceAssignmentProcessor
28 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceAssignmentUtils
29 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceDefinitionUtils.createResourceAssignments
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
32 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
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 data class ResourceResolutionResult(
50     val templateMap: MutableMap<String, String>,
51     val assignmentMap: MutableMap<String, JsonNode>
52 )
53
54 interface ResourceResolutionService {
55
56     fun registeredResourceSources(): List<String>
57
58     suspend fun resolveFromDatabase(
59         bluePrintRuntimeService: BluePrintRuntimeService<*>,
60         artifactTemplate: String,
61         resolutionKey: String
62     ): String
63
64     suspend fun resolveResolutionKeysFromDatabase(
65         bluePrintRuntimeService: BluePrintRuntimeService<*>,
66         artifactTemplate: String
67     ): List<String>
68
69     suspend fun resolveArtifactNamesAndResolutionKeysFromDatabase(
70         bluePrintRuntimeService: BluePrintRuntimeService<*>
71     ): Map<String, List<String>>
72
73     suspend fun resolveResources(
74         bluePrintRuntimeService: BluePrintRuntimeService<*>,
75         nodeTemplateName: String,
76         artifactNames: List<String>,
77         properties: Map<String, Any>,
78         stepName: String
79     ): ResourceResolutionResult
80
81     suspend fun resolveResources(
82         bluePrintRuntimeService: BluePrintRuntimeService<*>,
83         nodeTemplateName: String,
84         artifactPrefix: String,
85         properties: Map<String, Any>
86     ): Pair<String, MutableList<ResourceAssignment>>
87
88     /** Resolve resources for all the sources defined in a particular resource Definition[resolveDefinition]
89      * with other [resourceDefinitions] dependencies for the sources [sources]
90      * Used to get the same resource values from multiple sources. **/
91     suspend fun resolveResourceDefinition(
92         blueprintRuntimeService: BluePrintRuntimeService<*>,
93         resourceDefinitions: MutableMap<String, ResourceDefinition>,
94         resolveDefinition: String,
95         sources: List<String>
96     ):
97         MutableMap<String, JsonNode>
98
99     suspend fun resolveResourceAssignments(
100         blueprintRuntimeService: BluePrintRuntimeService<*>,
101         resourceDefinitions: MutableMap<String, ResourceDefinition>,
102         resourceAssignments: MutableList<ResourceAssignment>,
103         artifactPrefix: String,
104         properties: Map<String, Any>
105     )
106 }
107
108 @Service(ResourceResolutionConstants.SERVICE_RESOURCE_RESOLUTION)
109 open class ResourceResolutionServiceImpl(
110     private var applicationContext: ApplicationContext,
111     private var templateResolutionDBService: TemplateResolutionService,
112     private var blueprintTemplateService: BluePrintTemplateService,
113     private var resourceResolutionDBService: ResourceResolutionDBService
114 ) :
115     ResourceResolutionService {
116
117     private val log = LoggerFactory.getLogger(ResourceResolutionService::class.java)
118
119     override fun registeredResourceSources(): List<String> {
120         return applicationContext.getBeanNamesForType(ResourceAssignmentProcessor::class.java)
121             .filter { it.startsWith(ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR) }
122             .map { it.substringAfter(ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR) }
123     }
124
125     override suspend fun resolveFromDatabase(
126         bluePrintRuntimeService: BluePrintRuntimeService<*>,
127         artifactTemplate: String,
128         resolutionKey: String
129     ): String {
130         return templateResolutionDBService.findByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
131             bluePrintRuntimeService,
132             artifactTemplate,
133             resolutionKey
134         )
135     }
136
137     override suspend fun resolveResolutionKeysFromDatabase(
138         bluePrintRuntimeService: BluePrintRuntimeService<*>,
139         artifactTemplate: String
140     ): List<String> {
141         return templateResolutionDBService.findResolutionKeysByBlueprintNameAndBlueprintVersionAndArtifactName(
142             bluePrintRuntimeService,
143             artifactTemplate
144         )
145     }
146
147     override suspend fun resolveArtifactNamesAndResolutionKeysFromDatabase(
148         bluePrintRuntimeService: BluePrintRuntimeService<*>): Map<String, List<String>> {
149         return templateResolutionDBService.findArtifactNamesAndResolutionKeysByBlueprintNameAndBlueprintVersion(
150             bluePrintRuntimeService
151         )
152     }
153
154     override suspend fun resolveResources(
155         bluePrintRuntimeService: BluePrintRuntimeService<*>,
156         nodeTemplateName: String,
157         artifactNames: List<String>,
158         properties: Map<String, Any>,
159         stepName: String
160     ): ResourceResolutionResult {
161
162         val resourceAssignmentRuntimeService =
163             ResourceAssignmentUtils.transformToRARuntimeService(bluePrintRuntimeService, artifactNames.toString())
164
165         val templateMap: MutableMap<String, String> = hashMapOf()
166         val assignmentMap: MutableMap<String, JsonNode> = hashMapOf()
167         artifactNames.forEach { artifactName ->
168             val (resolvedStringContent, resourceAssignmentList) = resolveResources(
169                 resourceAssignmentRuntimeService, nodeTemplateName,
170                 artifactName, properties
171             )
172             val resolvedJsonContent = resourceAssignmentList
173                 .associateBy({ it.name }, { it.property?.value })
174                 .asJsonNode()
175
176             templateMap[artifactName] = resolvedStringContent
177             assignmentMap[artifactName] = resolvedJsonContent
178
179             val failedResolution = resourceAssignmentList.filter { it.status != "success" && it.property?.required == true }.map { it.name }
180             if (failedResolution.isNotEmpty()) {
181                 val errorMessages = mutableListOf("Failed to resolve required values: $failedResolution").apply {
182                     this.addAll(resourceAssignmentRuntimeService.getBluePrintError().allErrors())
183                 }
184                 bluePrintRuntimeService.getBluePrintError().addErrors(stepName, errorMessages)
185             }
186         }
187         return ResourceResolutionResult(templateMap, assignmentMap)
188     }
189
190     override suspend fun resolveResources(
191         bluePrintRuntimeService: BluePrintRuntimeService<*>,
192         nodeTemplateName: String,
193         artifactPrefix: String,
194         properties: Map<String, Any>
195     ): Pair<String, MutableList<ResourceAssignment>> {
196
197         // Template Artifact Definition Name
198         val artifactTemplate = "$artifactPrefix-template"
199         // Resource Assignment Artifact Definition Name
200         val artifactMapping = "$artifactPrefix-mapping"
201
202         log.info("Resolving resource with resource assignment artifact($artifactMapping)")
203
204         val resourceAssignmentContent =
205             bluePrintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, artifactMapping)
206
207         val resourceAssignments: MutableList<ResourceAssignment> =
208             JacksonUtils.getListFromJson(resourceAssignmentContent, ResourceAssignment::class.java)
209                 as? MutableList<ResourceAssignment>
210                 ?: throw BluePrintProcessorException("couldn't get Dictionary Definitions")
211
212         if (isToStore(properties)) {
213             val existingResourceResolution = isNewResolution(bluePrintRuntimeService, properties, artifactPrefix)
214             if (existingResourceResolution.isNotEmpty()) {
215                 updateResourceAssignmentWithExisting(
216                     bluePrintRuntimeService as ResourceAssignmentRuntimeService,
217                     existingResourceResolution, resourceAssignments
218                 )
219             }
220         }
221
222         // Get the Resource Dictionary Name
223         val resourceDefinitions: MutableMap<String, ResourceDefinition> = ResourceAssignmentUtils
224             .resourceDefinitions(bluePrintRuntimeService.bluePrintContext().rootPath)
225
226         // Resolve resources
227         resolveResourceAssignments(
228             bluePrintRuntimeService,
229             resourceDefinitions,
230             resourceAssignments,
231             artifactPrefix,
232             properties
233         )
234
235         val resolutionSummary = properties.getOrDefault(
236             ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY,
237             false
238         ) as Boolean
239
240         val resolvedParamJsonContent =
241             ResourceAssignmentUtils.generateResourceDataForAssignments(resourceAssignments.toList())
242         val artifactTemplateDefinition =
243             bluePrintRuntimeService.bluePrintContext().checkNodeTemplateArtifact(nodeTemplateName, artifactTemplate)
244
245         val resolvedContent = when {
246             artifactTemplateDefinition != null -> {
247                 blueprintTemplateService.generateContent(
248                     bluePrintRuntimeService, nodeTemplateName,
249                     artifactTemplate, resolvedParamJsonContent, false,
250                     mutableMapOf(
251                         ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE to
252                             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE]
253                                 .asJsonPrimitive()
254                     )
255                 )
256             }
257             resolutionSummary -> {
258                 ResourceAssignmentUtils.generateResolutionSummaryData(resourceAssignments, resourceDefinitions)
259             }
260             else -> {
261                 resolvedParamJsonContent
262             }
263         }
264
265         if (isToStore(properties)) {
266             templateResolutionDBService.write(properties, resolvedContent, bluePrintRuntimeService, artifactPrefix)
267             log.info("Template resolution saved into database successfully : ($properties)")
268         }
269
270         return Pair(resolvedContent, resourceAssignments)
271     }
272
273     override suspend fun resolveResourceDefinition(
274         blueprintRuntimeService: BluePrintRuntimeService<*>,
275         resourceDefinitions: MutableMap<String, ResourceDefinition>,
276         resolveDefinition: String,
277         sources: List<String>
278     ): MutableMap<String, JsonNode> {
279
280         // Populate Dummy Resource Assignments
281         val resourceAssignments = createResourceAssignments(resourceDefinitions, resolveDefinition, sources)
282
283         resolveResourceAssignments(
284             blueprintRuntimeService, resourceDefinitions, resourceAssignments,
285             UUID.randomUUID().toString(), hashMapOf()
286         )
287
288         // Get the data from Resource Assignments
289         return ResourceAssignmentUtils.generateResourceForAssignments(resourceAssignments)
290     }
291
292     /**
293      * Iterate the Batch, get the Resource Assignment, dictionary Name, Look for the Resource definition for the
294      * name, then get the type of the Resource Definition, Get the instance for the Resource Type and process the
295      * request.
296      */
297     override suspend fun resolveResourceAssignments(
298         blueprintRuntimeService: BluePrintRuntimeService<*>,
299         resourceDefinitions: MutableMap<String, ResourceDefinition>,
300         resourceAssignments: MutableList<ResourceAssignment>,
301         artifactPrefix: String,
302         properties: Map<String, Any>
303     ) {
304
305         val bulkSequenced = BulkResourceSequencingUtils.process(resourceAssignments)
306
307         // Check the BlueprintRuntime Service Should be ResourceAssignmentRuntimeService
308         val resourceAssignmentRuntimeService = if (blueprintRuntimeService !is ResourceAssignmentRuntimeService) {
309             ResourceAssignmentUtils.transformToRARuntimeService(blueprintRuntimeService, artifactPrefix)
310         } else {
311             blueprintRuntimeService
312         }
313
314         exposeOccurrencePropertyInResourceAssignments(resourceAssignmentRuntimeService, properties)
315
316         coroutineScope {
317             bulkSequenced.forEach { batchResourceAssignments ->
318                 // Execute Non Dependent Assignments in parallel ( ie asynchronously )
319                 val deferred = batchResourceAssignments
320                     .filter { it.name != "*" && it.name != "start" }
321                     .filter { it.status != BluePrintConstants.STATUS_SUCCESS }
322                     .map { resourceAssignment ->
323                         async {
324                             val dictionaryName = resourceAssignment.dictionaryName
325                             val dictionarySource = resourceAssignment.dictionarySource
326
327                             val processorName = processorName(dictionaryName!!, dictionarySource!!, resourceDefinitions)
328
329                             val resourceAssignmentProcessor =
330                                 applicationContext.getBean(processorName) as? ResourceAssignmentProcessor
331                                     ?: throw BluePrintProcessorException(
332                                         "failed to get resource processor ($processorName) " +
333                                             "for resource assignment(${resourceAssignment.name})"
334                                     )
335                             try {
336                                 // Set BluePrint Runtime Service
337                                 resourceAssignmentProcessor.raRuntimeService = resourceAssignmentRuntimeService
338                                 // Set Resource Dictionaries
339                                 resourceAssignmentProcessor.resourceDictionaries = resourceDefinitions
340
341                                 resourceAssignmentProcessor.resourceAssignments = resourceAssignments
342
343                                 // Invoke Apply Method
344                                 resourceAssignmentProcessor.applyNB(resourceAssignment)
345
346                                 if (isToStore(properties)) {
347                                     resourceResolutionDBService.write(
348                                         properties,
349                                         blueprintRuntimeService,
350                                         artifactPrefix,
351                                         resourceAssignment
352                                     )
353                                     log.info("Resource resolution saved into database successfully : (${resourceAssignment.name})")
354                                 }
355
356                                 // Set errors from RA
357                                 blueprintRuntimeService.setBluePrintError(resourceAssignmentRuntimeService.getBluePrintError())
358                             } catch (e: RuntimeException) {
359                                 log.error("Fail in processing ${resourceAssignment.name}", e)
360                                 throw BluePrintProcessorException(e)
361                             }
362                         }
363                     }
364                 log.debug("Resolving (${deferred.size})resources parallel.")
365                 deferred.awaitAll()
366             }
367         }
368     }
369
370     /**
371      * If the Source instance is "input", then it is not mandatory to have source Resource Definition, So it can
372      *  derive the default input processor.
373      */
374     private fun processorName(
375         dictionaryName: String,
376         dictionarySource: String,
377         resourceDefinitions: MutableMap<String, ResourceDefinition>
378     ): String {
379         val processorName: String = when (dictionarySource) {
380             "input" -> {
381                 "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-input"
382             }
383             "default" -> {
384                 "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-default"
385             }
386             else -> {
387                 val resourceDefinition = resourceDefinitions[dictionaryName]
388                     ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dictionaryName")
389
390                 val resourceSource = resourceDefinition.sources[dictionarySource]
391                     ?: throw BluePrintProcessorException("couldn't get resource definition $dictionaryName source($dictionarySource)")
392
393                 ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR.plus(resourceSource.type)
394             }
395         }
396         checkNotEmpty(processorName) {
397             "couldn't get processor name for resource dictionary definition($dictionaryName) source($dictionarySource)"
398         }
399
400         return processorName
401     }
402
403     // Check whether to store or not the resolution of resource and template
404     private fun isToStore(properties: Map<String, Any>): Boolean {
405         return properties.containsKey(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT) &&
406             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] as Boolean
407     }
408
409     // Check whether resolution already exist in the database for the specified resolution-key or resourceId/resourceType
410     private suspend fun isNewResolution(
411         bluePrintRuntimeService: BluePrintRuntimeService<*>,
412         properties: Map<String, Any>,
413         artifactPrefix: String
414     ): List<ResourceResolution> {
415         val occurrence = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] as Int
416         val resolutionKey = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] as String
417         val resourceId = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] as String
418         val resourceType = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] as String
419
420         if (resolutionKey.isNotEmpty()) {
421             val existingResourceAssignments =
422                 resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKeyAndOccurrence(
423                     bluePrintRuntimeService,
424                     resolutionKey,
425                     occurrence,
426                     artifactPrefix
427                 )
428             if (existingResourceAssignments.isNotEmpty()) {
429                 log.info(
430                     "Resolution with resolutionKey=($resolutionKey) already exist - will resolve all resources not already resolved.",
431                     resolutionKey
432                 )
433             }
434             return existingResourceAssignments
435         } else if (resourceId.isNotEmpty() && resourceType.isNotEmpty()) {
436             val existingResourceAssignments =
437                 resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResourceIdAndResourceTypeAndOccurrence(
438                     bluePrintRuntimeService,
439                     resourceId,
440                     resourceType,
441
442                     occurrence,
443                     artifactPrefix
444                 )
445             if (existingResourceAssignments.isNotEmpty()) {
446                 log.info(
447                     "Resolution with resourceId=($resourceId) and resourceType=($resourceType) already " +
448                         "exist - will resolve all resources not already resolved."
449                 )
450             }
451             return existingResourceAssignments
452         }
453         return emptyList()
454     }
455
456     // Update the resource assignment list with the status of the resource that have already been resolved
457     private fun updateResourceAssignmentWithExisting(
458         raRuntimeService: ResourceAssignmentRuntimeService,
459         resourceResolutionList: List<ResourceResolution>,
460         resourceAssignmentList: MutableList<ResourceAssignment>
461     ) {
462         resourceResolutionList.forEach { resourceResolution ->
463             if (resourceResolution.status == BluePrintConstants.STATUS_SUCCESS) {
464                 resourceAssignmentList.forEach {
465                     if (compareOne(resourceResolution, it)) {
466                         log.info(
467                             "Resource ({}) already resolved: value=({})", it.name,
468                             if (hasLogProtect(it.property)) LOG_REDACTED else resourceResolution.value
469                         )
470
471                         // Make sure to recreate value as per the defined type.
472                         val value = resourceResolution.value!!.asJsonType(it.property!!.type)
473                         it.property!!.value = value
474                         it.status = resourceResolution.status
475                         ResourceAssignmentUtils.setResourceDataValue(it, raRuntimeService, value)
476                     }
477                 }
478             }
479         }
480     }
481
482     // Comparision between what we have in the database vs what we have to assign.
483     private fun compareOne(resourceResolution: ResourceResolution, resourceAssignment: ResourceAssignment): Boolean {
484         return (
485             resourceResolution.name == resourceAssignment.name &&
486                 resourceResolution.dictionaryName == resourceAssignment.dictionaryName &&
487                 resourceResolution.dictionarySource == resourceAssignment.dictionarySource &&
488                 resourceResolution.dictionaryVersion == resourceAssignment.version
489             )
490     }
491
492     private fun exposeOccurrencePropertyInResourceAssignments(
493         raRuntimeService: ResourceAssignmentRuntimeService,
494         properties: Map<String, Any>
495     ) {
496         raRuntimeService.putResolutionStore(
497             ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE,
498             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
499         )
500     }
501 }