aboutsummaryrefslogtreecommitdiffstats
path: root/ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionService.kt
blob: f9dfd864d9a6e42dbea83f830db7a321e3af8bfa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
/*
 *  Copyright © 2017-2018 AT&T Intellectual Property.
 *  Modifications Copyright © 2018-2019 IBM, Bell Canada
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution

import com.fasterxml.jackson.databind.JsonNode
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.ResourceResolution
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.ResourceResolutionDBService
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.db.TemplateResolutionService
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.processor.ResourceAssignmentProcessor
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceAssignmentUtils
import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceDefinitionUtils.createResourceAssignments
import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
import org.onap.ccsdk.cds.controllerblueprints.core.asJsonNode
import org.onap.ccsdk.cds.controllerblueprints.core.asJsonPrimitive
import org.onap.ccsdk.cds.controllerblueprints.core.asJsonType
import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty
import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants.LOG_REDACTED
import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintTemplateService
import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils.Companion.hasLogProtect
import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition
import org.onap.ccsdk.cds.controllerblueprints.resource.dict.utils.BulkResourceSequencingUtils
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Service
import java.util.UUID

data class ResourceResolutionResult(
    val templateMap: MutableMap<String, String>,
    val assignmentMap: MutableMap<String, JsonNode>
)

interface ResourceResolutionService {

    fun registeredResourceSources(): List<String>

    suspend fun resolveFromDatabase(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        artifactTemplate: String,
        resolutionKey: String
    ): String

    suspend fun resolveResources(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        nodeTemplateName: String,
        artifactNames: List<String>,
        properties: Map<String, Any>
    ): ResourceResolutionResult

    suspend fun resolveResources(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        nodeTemplateName: String,
        artifactPrefix: String,
        properties: Map<String, Any>
    ): Pair<String, MutableList<ResourceAssignment>>

    /** Resolve resources for all the sources defined in a particular resource Definition[resolveDefinition]
     * with other [resourceDefinitions] dependencies for the sources [sources]
     * Used to get the same resource values from multiple sources. **/
    suspend fun resolveResourceDefinition(
        blueprintRuntimeService: BluePrintRuntimeService<*>,
        resourceDefinitions: MutableMap<String, ResourceDefinition>,
        resolveDefinition: String,
        sources: List<String>
    ):
        MutableMap<String, JsonNode>

    suspend fun resolveResourceAssignments(
        blueprintRuntimeService: BluePrintRuntimeService<*>,
        resourceDefinitions: MutableMap<String, ResourceDefinition>,
        resourceAssignments: MutableList<ResourceAssignment>,
        artifactPrefix: String,
        properties: Map<String, Any>
    )
}

@Service(ResourceResolutionConstants.SERVICE_RESOURCE_RESOLUTION)
open class ResourceResolutionServiceImpl(
    private var applicationContext: ApplicationContext,
    private var templateResolutionDBService: TemplateResolutionService,
    private var blueprintTemplateService: BluePrintTemplateService,
    private var resourceResolutionDBService: ResourceResolutionDBService
) :
    ResourceResolutionService {

    private val log = LoggerFactory.getLogger(ResourceResolutionService::class.java)

    override fun registeredResourceSources(): List<String> {
        return applicationContext.getBeanNamesForType(ResourceAssignmentProcessor::class.java)
            .filter { it.startsWith(ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR) }
            .map { it.substringAfter(ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR) }
    }

    override suspend fun resolveFromDatabase(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        artifactTemplate: String,
        resolutionKey: String
    ): String {
        return templateResolutionDBService.findByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
            bluePrintRuntimeService,
            artifactTemplate,
            resolutionKey
        )
    }

    override suspend fun resolveResources(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        nodeTemplateName: String,
        artifactNames: List<String>,
        properties: Map<String, Any>
    ): ResourceResolutionResult {

        val resourceAssignmentRuntimeService =
            ResourceAssignmentUtils.transformToRARuntimeService(bluePrintRuntimeService, artifactNames.toString())

        val templateMap: MutableMap<String, String> = hashMapOf()
        val assignmentMap: MutableMap<String, JsonNode> = hashMapOf()
        artifactNames.forEach { artifactName ->
            val (resolvedStringContent, resourceAssignmentList) = resolveResources(
                resourceAssignmentRuntimeService, nodeTemplateName,
                artifactName, properties
            )
            val resolvedJsonContent = resourceAssignmentList
                .associateBy({ it.name }, { it.property?.value })
                .asJsonNode()

            templateMap[artifactName] = resolvedStringContent
            assignmentMap[artifactName] = resolvedJsonContent

            val failedResolution = resourceAssignmentList.filter { it.status != "success" && it.property?.required == true }.map { it.name }
            if (failedResolution.isNotEmpty()) {
                // The following error message is returned by default to handle a scenario when
                // error message comes empty even when resolution has actually failed.
                // Example: input-source type resolution seems to fail with no error code.
                bluePrintRuntimeService.getBluePrintError().errors.add("Failed to resolve required resources($failedResolution)")
                bluePrintRuntimeService.getBluePrintError().errors.addAll(resourceAssignmentRuntimeService.getBluePrintError().errors)
            }
        }
        return ResourceResolutionResult(templateMap, assignmentMap)
    }

    override suspend fun resolveResources(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        nodeTemplateName: String,
        artifactPrefix: String,
        properties: Map<String, Any>
    ): Pair<String, MutableList<ResourceAssignment>> {

        // Template Artifact Definition Name
        val artifactTemplate = "$artifactPrefix-template"
        // Resource Assignment Artifact Definition Name
        val artifactMapping = "$artifactPrefix-mapping"

        log.info("Resolving resource with resource assignment artifact($artifactMapping)")

        val resourceAssignmentContent =
            bluePrintRuntimeService.resolveNodeTemplateArtifact(nodeTemplateName, artifactMapping)

        val resourceAssignments: MutableList<ResourceAssignment> =
            JacksonUtils.getListFromJson(resourceAssignmentContent, ResourceAssignment::class.java)
                as? MutableList<ResourceAssignment>
                ?: throw BluePrintProcessorException("couldn't get Dictionary Definitions")

        if (isToStore(properties)) {
            val existingResourceResolution = isNewResolution(bluePrintRuntimeService, properties, artifactPrefix)
            if (existingResourceResolution.isNotEmpty()) {
                updateResourceAssignmentWithExisting(
                    bluePrintRuntimeService as ResourceAssignmentRuntimeService,
                    existingResourceResolution, resourceAssignments
                )
            }
        }

        // Get the Resource Dictionary Name
        val resourceDefinitions: MutableMap<String, ResourceDefinition> = ResourceAssignmentUtils
            .resourceDefinitions(bluePrintRuntimeService.bluePrintContext().rootPath)

        // Resolve resources
        resolveResourceAssignments(
            bluePrintRuntimeService,
            resourceDefinitions,
            resourceAssignments,
            artifactPrefix,
            properties
        )

        val resolutionSummary = properties.getOrDefault(
            ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_SUMMARY,
            false
        ) as Boolean

        val resolvedParamJsonContent =
            ResourceAssignmentUtils.generateResourceDataForAssignments(resourceAssignments.toList())
        val artifactTemplateDefinition = bluePrintRuntimeService.bluePrintContext().checkNodeTemplateArtifact(nodeTemplateName, artifactTemplate)

        val resolvedContent = when {
            artifactTemplateDefinition != null -> {
                blueprintTemplateService.generateContent(
                        bluePrintRuntimeService, nodeTemplateName,
                        artifactTemplate, resolvedParamJsonContent, false,
                        mutableMapOf(
                                ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE to
                                        properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
                        )
                )
            }
            resolutionSummary -> {
                ResourceAssignmentUtils.generateResolutionSummaryData(resourceAssignments, resourceDefinitions)
            }
            else -> {
                resolvedParamJsonContent
            }
        }

        if (isToStore(properties)) {
            templateResolutionDBService.write(properties, resolvedContent, bluePrintRuntimeService, artifactPrefix)
            log.info("Template resolution saved into database successfully : ($properties)")
        }

        return Pair(resolvedContent, resourceAssignments)
    }

    override suspend fun resolveResourceDefinition(
        blueprintRuntimeService: BluePrintRuntimeService<*>,
        resourceDefinitions: MutableMap<String, ResourceDefinition>,
        resolveDefinition: String,
        sources: List<String>
    ): MutableMap<String, JsonNode> {

        // Populate Dummy Resource Assignments
        val resourceAssignments = createResourceAssignments(resourceDefinitions, resolveDefinition, sources)

        resolveResourceAssignments(
            blueprintRuntimeService, resourceDefinitions, resourceAssignments,
            UUID.randomUUID().toString(), hashMapOf()
        )

        // Get the data from Resource Assignments
        return ResourceAssignmentUtils.generateResourceForAssignments(resourceAssignments)
    }

    /**
     * Iterate the Batch, get the Resource Assignment, dictionary Name, Look for the Resource definition for the
     * name, then get the type of the Resource Definition, Get the instance for the Resource Type and process the
     * request.
     */
    override suspend fun resolveResourceAssignments(
        blueprintRuntimeService: BluePrintRuntimeService<*>,
        resourceDefinitions: MutableMap<String, ResourceDefinition>,
        resourceAssignments: MutableList<ResourceAssignment>,
        artifactPrefix: String,
        properties: Map<String, Any>
    ) {

        val bulkSequenced = BulkResourceSequencingUtils.process(resourceAssignments)

        // Check the BlueprintRuntime Service Should be ResourceAssignmentRuntimeService
        val resourceAssignmentRuntimeService = if (blueprintRuntimeService !is ResourceAssignmentRuntimeService) {
            ResourceAssignmentUtils.transformToRARuntimeService(blueprintRuntimeService, artifactPrefix)
        } else {
            blueprintRuntimeService
        }

        exposeOccurrencePropertyInResourceAssignments(resourceAssignmentRuntimeService, properties)

        coroutineScope {
            bulkSequenced.forEach { batchResourceAssignments ->
                // Execute Non Dependent Assignments in parallel ( ie asynchronously )
                val deferred = batchResourceAssignments
                    .filter { it.name != "*" && it.name != "start" }
                    .filter { it.status != BluePrintConstants.STATUS_SUCCESS }
                    .map { resourceAssignment ->
                        async {
                            val dictionaryName = resourceAssignment.dictionaryName
                            val dictionarySource = resourceAssignment.dictionarySource

                            val processorName = processorName(dictionaryName!!, dictionarySource!!, resourceDefinitions)

                            val resourceAssignmentProcessor =
                                applicationContext.getBean(processorName) as? ResourceAssignmentProcessor
                                    ?: throw BluePrintProcessorException(
                                        "failed to get resource processor ($processorName) " +
                                                "for resource assignment(${resourceAssignment.name})"
                                    )
                            try {
                                // Set BluePrint Runtime Service
                                resourceAssignmentProcessor.raRuntimeService = resourceAssignmentRuntimeService
                                // Set Resource Dictionaries
                                resourceAssignmentProcessor.resourceDictionaries = resourceDefinitions
                                // Invoke Apply Method
                                resourceAssignmentProcessor.applyNB(resourceAssignment)

                                if (isToStore(properties)) {
                                    resourceResolutionDBService.write(
                                        properties,
                                        blueprintRuntimeService,
                                        artifactPrefix,
                                        resourceAssignment
                                    )
                                    log.info("Resource resolution saved into database successfully : (${resourceAssignment.name})")
                                }

                                // Set errors from RA
                                blueprintRuntimeService.setBluePrintError(resourceAssignmentRuntimeService.getBluePrintError())
                            } catch (e: RuntimeException) {
                                log.error("Fail in processing ${resourceAssignment.name}", e)
                                throw BluePrintProcessorException(e)
                            }
                        }
                    }
                log.debug("Resolving (${deferred.size})resources parallel.")
                deferred.awaitAll()
            }
        }
    }

    /**
     * If the Source instance is "input", then it is not mandatory to have source Resource Definition, So it can
     *  derive the default input processor.
     */
    private fun processorName(
        dictionaryName: String,
        dictionarySource: String,
        resourceDefinitions: MutableMap<String, ResourceDefinition>
    ): String {
        val processorName: String = when (dictionarySource) {
            "input" -> {
                "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-input"
            }
            "default" -> {
                "${ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-default"
            }
            else -> {
                val resourceDefinition = resourceDefinitions[dictionaryName]
                    ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dictionaryName")

                val resourceSource = resourceDefinition.sources[dictionarySource]
                    ?: throw BluePrintProcessorException("couldn't get resource definition $dictionaryName source($dictionarySource)")

                ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR.plus(resourceSource.type)
            }
        }
        checkNotEmpty(processorName) {
            "couldn't get processor name for resource dictionary definition($dictionaryName) source($dictionarySource)"
        }

        return processorName
    }

    // Check whether to store or not the resolution of resource and template
    private fun isToStore(properties: Map<String, Any>): Boolean {
        return properties.containsKey(ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT) &&
                properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_STORE_RESULT] as Boolean
    }

    // Check whether resolution already exist in the database for the specified resolution-key or resourceId/resourceType
    private suspend fun isNewResolution(
        bluePrintRuntimeService: BluePrintRuntimeService<*>,
        properties: Map<String, Any>,
        artifactPrefix: String
    ): List<ResourceResolution> {
        val occurrence = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] as Int
        val resolutionKey = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOLUTION_KEY] as String
        val resourceId = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_ID] as String
        val resourceType = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_RESOURCE_TYPE] as String

        if (resolutionKey.isNotEmpty()) {
            val existingResourceAssignments =
                resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResolutionKeyAndOccurrence(
                    bluePrintRuntimeService,
                    resolutionKey,
                    occurrence,
                    artifactPrefix
                )
            if (existingResourceAssignments.isNotEmpty()) {
                log.info(
                    "Resolution with resolutionKey=($resolutionKey) already exist - will resolve all resources not already resolved.",
                    resolutionKey
                )
            }
            return existingResourceAssignments
        } else if (resourceId.isNotEmpty() && resourceType.isNotEmpty()) {
            val existingResourceAssignments =
                resourceResolutionDBService.findByBlueprintNameAndBlueprintVersionAndArtifactNameAndResourceIdAndResourceTypeAndOccurrence(
                    bluePrintRuntimeService,
                    resourceId,
                    resourceType,

                    occurrence,
                    artifactPrefix
                )
            if (existingResourceAssignments.isNotEmpty()) {
                log.info(
                    "Resolution with resourceId=($resourceId) and resourceType=($resourceType) already exist - will resolve " +
                            "all resources not already resolved."
                )
            }
            return existingResourceAssignments
        }
        return emptyList()
    }

    // Update the resource assignment list with the status of the resource that have already been resolved
    private fun updateResourceAssignmentWithExisting(
        raRuntimeService: ResourceAssignmentRuntimeService,
        resourceResolutionList: List<ResourceResolution>,
        resourceAssignmentList: MutableList<ResourceAssignment>
    ) {
        resourceResolutionList.forEach { resourceResolution ->
            if (resourceResolution.status == BluePrintConstants.STATUS_SUCCESS) {
                resourceAssignmentList.forEach {
                    if (compareOne(resourceResolution, it)) {
                        log.info(
                            "Resource ({}) already resolved: value=({})", it.name,
                            if (hasLogProtect(it.property)) LOG_REDACTED else resourceResolution.value
                        )

                        // Make sure to recreate value as per the defined type.
                        val value = resourceResolution.value!!.asJsonType(it.property!!.type)
                        it.property!!.value = value
                        it.status = resourceResolution.status
                        ResourceAssignmentUtils.setResourceDataValue(it, raRuntimeService, value)
                    }
                }
            }
        }
    }

    // Comparision between what we have in the database vs what we have to assign.
    private fun compareOne(resourceResolution: ResourceResolution, resourceAssignment: ResourceAssignment): Boolean {
        return (resourceResolution.name == resourceAssignment.name &&
                resourceResolution.dictionaryName == resourceAssignment.dictionaryName &&
                resourceResolution.dictionarySource == resourceAssignment.dictionarySource &&
                resourceResolution.dictionaryVersion == resourceAssignment.version)
    }

    private fun exposeOccurrencePropertyInResourceAssignments(
        raRuntimeService: ResourceAssignmentRuntimeService,
        properties: Map<String, Any>
    ) {
        raRuntimeService.putResolutionStore(
            ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE,
            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
        )
    }
}