Enable versioned resource resolution by using occurrence 03/129303/9
authorkuldipr <kuldip.rai@amdocs.com>
Fri, 18 Mar 2022 14:35:56 +0000 (10:35 -0400)
committerkuldipr <kuldip.rai@amdocs.com>
Tue, 24 May 2022 15:04:45 +0000 (11:04 -0400)
By setting occurrence to <= 0 in the CBA, a user indicates that each
time resource resolution executes, it should produce a new set of
values for a resolution-key or (resourceId, resourceType) pair.
For each new execution, the occurrence is incremented to serve
as the version number for the new set of values.

Issue-ID: CCSDK-3663
Signed-off-by: kuldipr <kuldip.rai@amdocs.com>
Change-Id: Ib535b20cb775dcbb5b02fe5a5f6904a335fda310

ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionComponent.kt
ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionService.kt
ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionDBService.kt
ms/blueprintsprocessor/functions/resource-resolution/src/main/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/db/ResourceResolutionRepository.kt
ms/blueprintsprocessor/functions/resource-resolution/src/test/kotlin/org/onap/ccsdk/cds/blueprintsprocessor/functions/resource/resolution/ResourceResolutionServiceTest.kt
ms/blueprintsprocessor/functions/resource-resolution/src/test/resources/payload/requests/sample-resourceresolution-request2.json [new file with mode: 0644]

index e060cdc..d46f75e 100644 (file)
@@ -109,9 +109,13 @@ open class ResourceResolutionComponent(private val resourceResolutionService: Re
 
         val artifactPrefixNamesNode = getOperationInput(ResourceResolutionConstants.INPUT_ARTIFACT_PREFIX_NAMES)
         val artifactPrefixNames = JacksonUtils.getListFromJsonNode(artifactPrefixNamesNode, String::class.java)
+        val alwaysPerformNewResolution = occurrence <= 0
+        val resolutionsToPerform: Int = if (alwaysPerformNewResolution) 1 else occurrence
+        for (j in 1..resolutionsToPerform) {
 
-        for (j in 1..occurrence) {
-            properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = j
+            if (!alwaysPerformNewResolution) {
+                properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = j
+            }
 
             val result = resourceResolutionService.resolveResources(
                 bluePrintRuntimeService,
@@ -121,8 +125,8 @@ open class ResourceResolutionComponent(private val resourceResolutionService: Re
                 stepName
             )
 
-            // provide indexed result in output if we have multiple resolution
-            if (occurrence != 1) {
+            // provide indexed result in output if we have multiple resolution.
+            if (resolutionsToPerform != 1) {
                 jsonResponse.set<JsonNode>(j.toString(), result.templateMap.asJsonNode())
                 assignmentMap.set<JsonNode>(j.toString(), result.assignmentMap.asJsonNode())
             } else {
index 8923a11..a3c1378 100644 (file)
@@ -201,6 +201,7 @@ open class ResourceResolutionServiceImpl(
         val artifactMapping = "$artifactPrefix-mapping"
         val forceResolution = isForceResolution(properties)
 
+        val propertiesMutableMap = properties.toMutableMap()
         log.info("Resolving resource with resource assignment artifact($artifactMapping)")
 
         val resourceAssignmentContent =
@@ -212,7 +213,16 @@ open class ResourceResolutionServiceImpl(
                 ?: throw BluePrintProcessorException("couldn't get Dictionary Definitions")
 
         if (isToStore(properties)) {
-            val existingResourceResolution = isNewResolution(bluePrintRuntimeService, properties, artifactPrefix)
+            val alwaysPerformNewResolution = properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] as Int <= 0
+            val existingResourceResolution = if (alwaysPerformNewResolution) {
+                val occurrence = findNextOccurrence(bluePrintRuntimeService, properties, artifactPrefix)
+                log.info("Always perform new resolutions  - next occurrence: $occurrence")
+                propertiesMutableMap[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = occurrence
+                // Since we are performing new resolution, simply pass empty list.
+                emptyList()
+            } else {
+                isNewResolution(bluePrintRuntimeService, properties, artifactPrefix)
+            }
             if (existingResourceResolution.isNotEmpty()) {
                 if (forceResolution) {
                     resourceResolutionDBService.deleteResourceResolutionList(existingResourceResolution)
@@ -237,7 +247,7 @@ open class ResourceResolutionServiceImpl(
             resourceDefinitions,
             resourceAssignments,
             artifactPrefix,
-            properties
+            propertiesMutableMap
         )
 
         val resolutionSummary = properties.getOrDefault(
@@ -271,7 +281,7 @@ open class ResourceResolutionServiceImpl(
         }
 
         if (isToStore(properties)) {
-            templateResolutionDBService.write(properties, resolvedContent, bluePrintRuntimeService, artifactPrefix)
+            templateResolutionDBService.write(propertiesMutableMap, resolvedContent, bluePrintRuntimeService, artifactPrefix)
             log.info("Template resolution saved into database successfully : ($properties)")
         }
 
@@ -490,7 +500,7 @@ open class ResourceResolutionServiceImpl(
         }
     }
 
-    // Comparision between what we have in the database vs what we have to assign.
+    // Comparison 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 &&
@@ -509,4 +519,48 @@ open class ResourceResolutionServiceImpl(
             properties[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE].asJsonPrimitive()
         )
     }
+
+    /**
+     * This method returns 'occurrence' required to persist new resource resolution.
+     *
+     * @param bluePrintRuntimeService
+     * @param properties
+     * @param artifactPrefix
+     */
+    private suspend fun findNextOccurrence(
+        bluePrintRuntimeService: BluePrintRuntimeService<*>,
+        properties: Map<String, Any>,
+        artifactPrefix: String
+    ): Int {
+        val metadata = bluePrintRuntimeService.bluePrintContext().metadata!!
+        val blueprintVersion = metadata[BluePrintConstants.METADATA_TEMPLATE_VERSION]!!
+        val blueprintName = metadata[BluePrintConstants.METADATA_TEMPLATE_NAME]!!
+        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
+
+        // This should not happen since the request has already been validated but worth to check it here as well.
+        if (resourceType.isEmpty() && resourceId.isEmpty() && resolutionKey.isEmpty()) {
+            throw BluePrintProcessorException(
+                "Can't proceed to get next occurrence: " +
+                    "Either provide a resolution-key OR combination of resource-id and resource-type"
+            )
+        }
+
+        if (resolutionKey.isNotEmpty()) {
+            return resourceResolutionDBService.findNextOccurrenceByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
+                resolutionKey,
+                blueprintName,
+                blueprintVersion,
+                artifactPrefix
+            )
+        } else {
+            return resourceResolutionDBService.findNextOccurrenceByBlueprintNameAndBlueprintVersionAndResourceIdAndResourceType(
+                blueprintName,
+                blueprintVersion,
+                resourceId,
+                resourceType
+            )
+        }
+    }
 }
index b5d4e45..1f0171f 100644 (file)
@@ -229,4 +229,52 @@ class ResourceResolutionDBService(private val resourceResolutionRepository: Reso
             throw BluePrintException("Failed to batch delete resource resolution", ex)
         }
     }
+
+    /**
+     * This method returns the (highest occurrence + 1) of resource resolutions if present in DB, returns 1 otherwise.
+     * The 'occurrence' is used to persist new resource resolution in the DB.
+     *
+     * @param resolutionKey
+     * @param blueprintName
+     * @param blueprintVersion
+     * @param artifactPrefix
+     */
+    suspend fun findNextOccurrenceByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
+        resolutionKey: String,
+        blueprintName: String,
+        blueprintVersion: String,
+        artifactPrefix: String
+    ) = withContext(Dispatchers.IO) {
+        val maxOccurrence = resourceResolutionRepository.findMaxOccurrenceByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
+            resolutionKey,
+            blueprintName,
+            blueprintVersion,
+            artifactPrefix
+        )
+        maxOccurrence?.inc() ?: 1
+    }
+
+    /**
+     * This method returns the (highest occurrence + 1) of resource resolutions if present in DB, returns 1 otherwise.
+     * The 'occurrence' is used to persist new resource resolution in the DB.
+     *
+     * @param blueprintName
+     * @param blueprintVersion
+     * @param resourceId
+     * @param resourceType
+     */
+    suspend fun findNextOccurrenceByBlueprintNameAndBlueprintVersionAndResourceIdAndResourceType(
+        blueprintName: String,
+        blueprintVersion: String,
+        resourceId: String,
+        resourceType: String
+    ) = withContext(Dispatchers.IO) {
+        val maxOccurrence = resourceResolutionRepository.findMaxOccurrenceByBlueprintNameAndBlueprintVersionAndResourceIdAndResourceType(
+            blueprintName,
+            blueprintVersion,
+            resourceId,
+            resourceType
+        )
+        maxOccurrence?.inc() ?: 1
+    }
 }
index 6e0ed3a..8513bda 100644 (file)
@@ -36,6 +36,28 @@ interface ResourceResolutionRepository : JpaRepository<ResourceResolution, Strin
         @Param("name")name: String
     ): ResourceResolution?
 
+    @Query(
+        value = "SELECT max(occurrence) FROM RESOURCE_RESOLUTION WHERE resolution_key = :key AND blueprint_name = :blueprintName AND blueprint_version = :blueprintVersion AND artifact_name = :artifactName ",
+        nativeQuery = true
+    )
+    fun findMaxOccurrenceByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
+        @Param("key")key: String,
+        @Param("blueprintName")blueprintName: String,
+        @Param("blueprintVersion")blueprintVersion: String,
+        @Param("artifactName")artifactName: String
+    ): Int?
+
+    @Query(
+        value = "SELECT max(occurrence) FROM RESOURCE_RESOLUTION WHERE blueprint_name = :blueprintName AND blueprint_version = :blueprintVersion AND resource_id = :resourceId AND resource_type = :resourceType ",
+        nativeQuery = true
+    )
+    fun findMaxOccurrenceByBlueprintNameAndBlueprintVersionAndResourceIdAndResourceType(
+        @Param("blueprintName")blueprintName: String,
+        @Param("blueprintVersion")blueprintVersion: String,
+        @Param("resourceId")resourceId: String,
+        @Param("resourceType")resourceType: String
+    ): Int?
+
     fun findByResolutionKeyAndBlueprintNameAndBlueprintVersionAndArtifactName(
         resolutionKey: String,
         blueprintName: String,
index 3d2a975..a801a7e 100644 (file)
@@ -153,6 +153,195 @@ class ResourceResolutionServiceTest {
         }
     }
 
+    /**
+     * Always perform new resolution even if resolution exists in the database.
+     */
+    @Test
+    @Throws(Exception::class)
+    fun testResolveResourceAlwaysPerformNewResolution() {
+        runBlocking {
+            // Occurrence <= 0 indicates to perform new resolution even if resolution exists in the database.
+            props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = -1
+            Assert.assertNotNull("failed to create ResourceResolutionService", resourceResolutionService)
+
+            val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime(
+                "1234",
+                "./../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration"
+            )
+
+            // Request#1
+            val executionServiceInput =
+                JacksonUtils.readValueFromClassPathFile(
+                    "payload/requests/sample-resourceresolution-request.json",
+                    ExecutionServiceInput::class.java
+                )!!
+
+            val resourceAssignmentRuntimeService =
+                ResourceAssignmentUtils.transformToRARuntimeService(
+                    bluePrintRuntimeService,
+                    "testResolveResource"
+                )
+
+            // Prepare inputs from Request#1
+            PayloadUtils.prepareInputsFromWorkflowPayload(
+                bluePrintRuntimeService,
+                executionServiceInput.payload,
+                "resource-assignment"
+            )
+
+            // Resolve resources as per Request#1
+            resourceResolutionService.resolveResources(
+                resourceAssignmentRuntimeService,
+                "resource-assignment",
+                "baseconfig",
+                props
+            )
+
+            // Request#2
+            val executionServiceInput2 =
+                JacksonUtils.readValueFromClassPathFile(
+                    "payload/requests/sample-resourceresolution-request2.json",
+                    ExecutionServiceInput::class.java
+                )!!
+
+            // Prepare inputs from Request#2
+            PayloadUtils.prepareInputsFromWorkflowPayload(
+                bluePrintRuntimeService,
+                executionServiceInput2.payload,
+                "resource-assignment"
+            )
+
+            // Resolve resources as per Request#2
+            resourceResolutionService.resolveResources(
+                resourceAssignmentRuntimeService,
+                "resource-assignment",
+                "baseconfig",
+                props
+            )
+        }.let { (templateMap, assignmentList) ->
+            assertEquals("This is Sample Velocity Template", templateMap)
+
+            val assignmentListForRequest1 = mutableListOf(
+                "service-instance-id" to "siid_1234",
+                "vnf-id" to "vnf_1234",
+                "vnf_name" to "temp_vnf"
+            )
+            val assignmentListForRequest2 = mutableListOf(
+                "service-instance-id" to "siid_new_resolution",
+                "vnf-id" to "vnf_new_resolution",
+                "vnf_name" to "temp_vnf_new_resolution"
+            )
+            assertEquals(assignmentListForRequest1.size, assignmentList.size)
+            assertEquals(assignmentListForRequest2.size, assignmentList.size)
+
+            // AlwaysPerformNewResolution use case - resolution request #2 should returns the resolution as per
+            // assignmentListForRequest2 since new resolution is performed.
+            var areEqual = assignmentListForRequest1.zip(assignmentList).all { (it1, it2) ->
+                it1.first == it2.name && it1.second == it2.property?.value?.asText() ?: null
+            }
+            assertEquals(false, areEqual)
+
+            areEqual = assignmentListForRequest2.zip(assignmentList).all { (it1, it2) ->
+                it1.first == it2.name && it1.second == it2.property?.value?.asText() ?: null
+            }
+            assertEquals(true, areEqual)
+        }
+    }
+
+    /**
+     * Don't perform new resolution in case resolution already exists in the database.
+     */
+    @Test
+    @Throws(Exception::class)
+    fun testResolveResourceNoNewResolution() {
+        runBlocking {
+            // Occurrence > 0 indicates to not perform new resolution if resolution exists in the database.
+            props[ResourceResolutionConstants.RESOURCE_RESOLUTION_INPUT_OCCURRENCE] = 1
+            Assert.assertNotNull("failed to create ResourceResolutionService", resourceResolutionService)
+
+            val bluePrintRuntimeService = BluePrintMetadataUtils.getBluePrintRuntime(
+                "1234",
+                "./../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration"
+            )
+
+            // Request#1
+            val executionServiceInput =
+                JacksonUtils.readValueFromClassPathFile(
+                    "payload/requests/sample-resourceresolution-request.json",
+                    ExecutionServiceInput::class.java
+                )!!
+
+            val resourceAssignmentRuntimeService =
+                ResourceAssignmentUtils.transformToRARuntimeService(
+                    bluePrintRuntimeService,
+                    "testResolveResource"
+                )
+
+            // Prepare inputs from Request#1
+            PayloadUtils.prepareInputsFromWorkflowPayload(
+                bluePrintRuntimeService,
+                executionServiceInput.payload,
+                "resource-assignment"
+            )
+            // Resolve resources as per Request#1
+            resourceResolutionService.resolveResources(
+                resourceAssignmentRuntimeService,
+                "resource-assignment",
+                "baseconfig",
+                props
+            )
+
+            // Request#2
+            val executionServiceInput2 =
+                JacksonUtils.readValueFromClassPathFile(
+                    "payload/requests/sample-resourceresolution-request2.json",
+                    ExecutionServiceInput::class.java
+                )!!
+
+            // Prepare inputs from Request#2
+            PayloadUtils.prepareInputsFromWorkflowPayload(
+                bluePrintRuntimeService,
+                executionServiceInput2.payload,
+                "resource-assignment"
+            )
+
+            // Resolve resources as per Request#2
+            resourceResolutionService.resolveResources(
+                resourceAssignmentRuntimeService,
+                "resource-assignment",
+                "baseconfig",
+                props
+            )
+        }.let { (templateMap, assignmentList) ->
+            assertEquals("This is Sample Velocity Template", templateMap)
+
+            val assignmentListForRequest1 = mutableListOf(
+                "service-instance-id" to "siid_1234",
+                "vnf-id" to "vnf_1234",
+                "vnf_name" to "temp_vnf"
+            )
+            val assignmentListForRequest2 = mutableListOf(
+                "service-instance-id" to "siid_new_resolution",
+                "vnf-id" to "vnf_new_resolution",
+                "vnf_name" to "temp_vnf_new_resolution"
+            )
+            assertEquals(assignmentListForRequest1.size, assignmentList.size)
+            assertEquals(assignmentListForRequest2.size, assignmentList.size)
+
+            //  NoNewResolution use case - resolution for request #2 returns the same resolution  as
+            //  assignmentListForRequest1 since no new resolution is/was actually performed.
+            var areEqual = assignmentListForRequest1.zip(assignmentList).all { (it1, it2) ->
+                it1.first == it2.name && it1.second == it2.property?.value?.asText() ?: null
+            }
+            assertEquals(true, areEqual)
+
+            areEqual = assignmentListForRequest2.zip(assignmentList).all { (it1, it2) ->
+                it1.first == it2.name && it1.second == it2.property?.value?.asText() ?: null
+            }
+            assertEquals(false, areEqual)
+        }
+    }
+
     @Test
     @Throws(Exception::class)
     fun testResolveResources() {
diff --git a/ms/blueprintsprocessor/functions/resource-resolution/src/test/resources/payload/requests/sample-resourceresolution-request2.json b/ms/blueprintsprocessor/functions/resource-resolution/src/test/resources/payload/requests/sample-resourceresolution-request2.json
new file mode 100644 (file)
index 0000000..7264775
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "actionIdentifiers": {
+    "actionName": "sample-action",
+    "blueprintName": "sample-blueprint",
+    "blueprintVersion": "1.0.0",
+    "mode": "sync"
+  },
+  "commonHeader": {
+    "flags": {
+      "isForce": true,
+      "ttl": 3600
+    },
+    "originatorId": "unit_tests",
+    "requestId": "123456-1001",
+    "subRequestId": "sub-123456-1001"
+  },
+  "payload": {
+    "resource-assignment-request": {
+      "resource-assignment-properties": {
+        "request-id": "1234",
+        "profile_name": "1.0.0",
+        "service-instance-id": "siid_new_resolution",
+        "vnf-id": "vnf_new_resolution",
+        "action-name": "assign-activate",
+        "scope-type": "vnf-type",
+        "hostname": "localhost",
+        "vnf_name": "temp_vnf_new_resolution"
+      }
+    }
+  }
+}