CDS max-occurrence feature 68/130168/10
authorJuhi Arora <juhi.arora1@bell.ca>
Mon, 6 Jun 2022 17:30:03 +0000 (13:30 -0400)
committerkuldipr <kuldip.rai@amdocs.com>
Thu, 1 Sep 2022 12:48:15 +0000 (08:48 -0400)
As part of occurrence feature,  one or more version of the resource
resolution can be resolved. However, user did not have granular
control in case the user wants to resolve a specific value once and
never again.

Max-Occurrence feature implements the granular control to be give the
user an option to specify the max number of times a resource to be
resolved. It is specified as part of mapping in a cba. Max-occurrence
value of 0 or not specifying it explicitly denotes the current default
behaviour of unlimited resoltions. If a user specify a particular
max-occurrence value then the resource is resolved that many times in
subsquent requests and never again once we reached the max-occurrence
limit of resource resolutions.

Issue-ID: CCSDK-3736
Change-Id: Ie18764a313530e36be14531d8c7b93bf54f0b651
Signed-off-by: kuldipr <kuldip.rai@amdocs.com>
components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/activation-blueprint.json
components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/data_types.json
components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/maxoccurrence-mapping.json [new file with mode: 0644]
components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Templates/maxoccurrence-template.vtl [new file with mode: 0644]
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-request.json
ms/blueprintsprocessor/functions/resource-resolution/src/test/resources/payload/requests/sample-resourceresolution-request2.json
ms/blueprintsprocessor/modules/blueprints/resource-dict/src/main/kotlin/org/onap/ccsdk/cds/controllerblueprints/resource/dict/ResourceDefinition.kt

index 85a056c..c506ad0 100644 (file)
             "type": "artifact-mapping-resource",
             "file": "Definitions/another-mapping.json"
           },
+          "maxoccurrence-template": {
+            "type": "artifact-template-velocity",
+            "file": "Templates/maxoccurrence-template.vtl"
+          },
+          "maxoccurrence-mapping": {
+            "type": "artifact-mapping-resource",
+            "file": "Definitions/maxoccurrence-mapping.json"
+          },
           "notemplate-mapping": {
             "type": "artifact-mapping-resource",
             "file": "Definitions/notemplate-mapping.json"
index 6d771cd..94c587a 100644 (file)
         "vnf_name": {
           "required": true,
           "type": "string"
+        },
+        "firmware-version": {
+          "type" : "string"
+        },
+        "ip-address": {
+          "type" : "string"
         }
       },
       "derived_from": "tosca.datatypes.Dynamic"
diff --git a/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/maxoccurrence-mapping.json b/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Definitions/maxoccurrence-mapping.json
new file mode 100644 (file)
index 0000000..7294885
--- /dev/null
@@ -0,0 +1,23 @@
+[
+  {
+    "name": "firmware-version",
+    "input-param": true,
+    "property": {
+      "type": "string"
+    },
+    "dictionary-name": "input-source",
+    "dictionary-source": "input",
+    "dependencies": []
+  },
+  {
+    "name": "ip-address",
+    "input-param": true,
+    "property": {
+      "type": "string"
+    },
+    "max-occurrence": 1,
+    "dictionary-name": "input-source",
+    "dictionary-source": "input",
+    "dependencies": []
+  }
+]
\ No newline at end of file
diff --git a/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Templates/maxoccurrence-template.vtl b/components/model-catalog/blueprint-model/test-blueprint/baseconfiguration/Templates/maxoccurrence-template.vtl
new file mode 100644 (file)
index 0000000..9a9fc27
--- /dev/null
@@ -0,0 +1 @@
+This is maxoccurrence Velocity Template
\ No newline at end of file
index a3c1378..427d48f 100644 (file)
@@ -218,7 +218,18 @@ open class ResourceResolutionServiceImpl(
                 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.
+                // If resolution has already been performed previously then may need to update some assignments
+                val resourceAssignmentFilteredMap: Map<String, ResourceAssignment> =
+                    resourceAssignments.filter { it.maxOccurrence != null && it.maxOccurrence!! in 1 until occurrence }
+                        .associateBy { it.name }
+                if (occurrence > 1 && resourceAssignmentFilteredMap.isNotEmpty())
+                    updateResourceAssignmentByOccurrence(
+                        bluePrintRuntimeService as ResourceAssignmentRuntimeService,
+                        propertiesMutableMap,
+                        artifactPrefix,
+                        resourceAssignmentFilteredMap as MutableMap<String, ResourceAssignment>
+                    )
+                // we may be performing new resolution for some resources, simply pass an empty list.
                 emptyList()
             } else {
                 isNewResolution(bluePrintRuntimeService, properties, artifactPrefix)
@@ -500,6 +511,92 @@ open class ResourceResolutionServiceImpl(
         }
     }
 
+    /**
+     * Utility to update the resourceAssignments with existing resolutions if maxOccurrence is defined on it.
+     */
+    private suspend fun updateResourceAssignmentByOccurrence(
+        raRuntimeService: ResourceAssignmentRuntimeService,
+        properties: Map<String, Any>,
+        artifactPrefix: String,
+        resourceAssignmentFilteredMap: MutableMap<String, ResourceAssignment>
+    ) {
+        val resourceResolutionMap =
+            getResourceResolutionByLastOccurrence(raRuntimeService, properties, artifactPrefix).associateBy { it.name }
+
+        resourceAssignmentFilteredMap
+            .filter {
+                resourceResolutionMap.containsKey(it.key) && compareOne(
+                    resourceResolutionMap[it.key]!!,
+                    it.value
+                )
+            }
+            .forEach { raMapEntry ->
+                val resourceResolution = resourceResolutionMap[raMapEntry.key]
+                val resourceAssignment = raMapEntry.value
+
+                resourceResolution?.value?.let {
+                    val value = it.asJsonType(resourceAssignment.property!!.type)
+                    resourceAssignment.property!!.value = value
+                    ResourceAssignmentUtils.setResourceDataValue(
+                        resourceAssignment,
+                        raRuntimeService,
+                        value
+                    )
+                }
+                resourceAssignment.status = resourceResolution?.status
+                resourceResolutionDBService.write(
+                    properties,
+                    raRuntimeService,
+                    artifactPrefix,
+                    resourceAssignment
+                )
+            }
+    }
+
+    /**
+     * Utility to find resource resolution based on occurrence.
+     */
+    private suspend fun getResourceResolutionByLastOccurrence(
+        bluePrintRuntimeService: BluePrintRuntimeService<*>,
+        properties: Map<String, Any>,
+        artifactPrefix: String
+    ): List<ResourceResolution> {
+
+        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 last occurrence: " +
+                    "Either provide a resolution-key OR combination of resource-id and resource-type"
+            )
+        }
+        val metadata = bluePrintRuntimeService.bluePrintContext().metadata!!
+        val blueprintVersion = metadata[BluePrintConstants.METADATA_TEMPLATE_VERSION]!!
+        val blueprintName = metadata[BluePrintConstants.METADATA_TEMPLATE_NAME]!!
+
+        return if (resolutionKey.isNotEmpty()) {
+            resourceResolutionDBService.findLastNOccurrences(
+                blueprintName,
+                blueprintVersion,
+                artifactPrefix,
+                resolutionKey,
+                1
+            ).takeIf { it.isNotEmpty() }?.values?.first() ?: emptyList()
+        } else {
+            resourceResolutionDBService.findLastNOccurrences(
+                blueprintName,
+                blueprintVersion,
+                artifactPrefix,
+                resourceId,
+                resourceType,
+                1
+            ).takeIf { it.isNotEmpty() }?.values?.first() ?: emptyList()
+        }
+    }
+
     // Comparison between what we have in the database vs what we have to assign.
     private fun compareOne(resourceResolution: ResourceResolution, resourceAssignment: ResourceAssignment): Boolean {
         return (
index aa7b61f..bd8f9d2 100644 (file)
@@ -169,6 +169,35 @@ class ResourceResolutionDBService(private val resourceResolutionRepository: Reso
         ).groupBy(ResourceResolution::occurrence).toSortedMap(reverseOrder())
     }
 
+    /**
+     * This returns the resolutions of last N 'occurrences'.
+     *
+     * @param blueprintName
+     * @param blueprintVersion
+     * @param artifactPrefix
+     * @param resourceId
+     * @param resourceType
+     * @param lastN
+     */
+    suspend fun findLastNOccurrences(
+        blueprintName: String,
+        blueprintVersion: String,
+        artifactPrefix: String,
+        resourceId: String,
+        resourceType: String,
+        lastN: Int
+    ): Map<Int, List<ResourceResolution>> = withContext(Dispatchers.IO) {
+
+        resourceResolutionRepository.findLastNOccurrences(
+            resourceId,
+            resourceType,
+            blueprintName,
+            blueprintVersion,
+            artifactPrefix,
+            lastN
+        ).groupBy(ResourceResolution::occurrence).toSortedMap(reverseOrder())
+    }
+
     /**
      * This returns the resolutions with 'occurrence' value between begin and end.
      *
index 9317a71..5861cf8 100644 (file)
@@ -75,6 +75,29 @@ interface ResourceResolutionRepository : JpaRepository<ResourceResolution, Strin
         @Param("lastN")begin: Int
     ): List<ResourceResolution>
 
+    @Query(
+        value = """
+        SELECT * FROM RESOURCE_RESOLUTION WHERE resource_id = :resourceId
+            AND resource_type =:resourceType AND blueprint_name = :blueprintName
+            AND blueprint_version = :blueprintVersion AND artifact_name = :artifactName
+            AND occurrence > (
+                select max(occurrence) - :lastN from RESOURCE_RESOLUTION
+                WHERE resource_id = :resourceId
+                    AND resource_type =:resourceType AND blueprint_name = :blueprintName
+                    AND blueprint_version = :blueprintVersion AND artifact_name = :artifactName)
+                    ORDER BY occurrence DESC, creation_date DESC
+          """,
+        nativeQuery = true
+    )
+    fun findLastNOccurrences(
+        @Param("resourceId")resourceId: String,
+        @Param("resourceType")resourceType: String,
+        @Param("blueprintName")blueprintName: String,
+        @Param("blueprintVersion")blueprintVersion: String,
+        @Param("artifactName")artifactName: String,
+        @Param("lastN")begin: Int
+    ): List<ResourceResolution>
+
     @Query(
         value = """
         SELECT * FROM RESOURCE_RESOLUTION WHERE resolution_key = :key
index a801a7e..b39e709 100644 (file)
@@ -248,6 +248,116 @@ class ResourceResolutionServiceTest {
         }
     }
 
+    /**
+     * Always perform new resolution even if resolution exists in the database.
+     */
+    @Test
+    @Throws(Exception::class)
+    fun testResolveResourcesForMaxOccurrence() {
+        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)
+
+            // Run time for Request#1
+            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
+                )!!
+
+            // Prepare inputs from Request#1
+            PayloadUtils.prepareInputsFromWorkflowPayload(
+                bluePrintRuntimeService,
+                executionServiceInput.payload,
+                "resource-assignment"
+            )
+
+            val resourceAssignmentRuntimeService =
+                ResourceAssignmentUtils.transformToRARuntimeService(
+                    bluePrintRuntimeService,
+                    "testResolveResource"
+                )
+
+            // Resolve resources as per Request#1
+            resourceResolutionService.resolveResources(
+                resourceAssignmentRuntimeService,
+                "resource-assignment",
+                "maxoccurrence",
+                props
+            )
+
+            // Run time for Request#2
+            val bluePrintRuntimeService2 = BluePrintMetadataUtils.getBluePrintRuntime(
+                "1234",
+                "./../../../../components/model-catalog/blueprint-model/test-blueprint/baseconfiguration"
+            )
+
+            // Request#2
+            val executionServiceInput2 =
+                JacksonUtils.readValueFromClassPathFile(
+                    "payload/requests/sample-resourceresolution-request2.json",
+                    ExecutionServiceInput::class.java
+                )!!
+
+            val resourceAssignmentRuntimeService2 =
+                ResourceAssignmentUtils.transformToRARuntimeService(
+                    bluePrintRuntimeService2,
+                    "testResolveResource"
+                )
+
+            // Prepare inputs from Request#2
+            PayloadUtils.prepareInputsFromWorkflowPayload(
+                bluePrintRuntimeService2,
+                executionServiceInput2.payload,
+                "resource-assignment"
+            )
+
+            // Resolve resources as per Request#2
+            resourceResolutionService.resolveResources(
+                resourceAssignmentRuntimeService2,
+                "resource-assignment",
+                "maxoccurrence",
+                props
+            )
+        }.let { (template, assignmentList) ->
+            assertEquals("This is maxoccurrence Velocity Template", template)
+
+            val assignmentListForRequest1 = mutableListOf(
+                "firmware-version" to "firmware-version-0",
+                "ip-address" to "192.0.0.1"
+            )
+            val assignmentListForRequest2 = mutableListOf(
+                "firmware-version" to "firmware-version-1",
+                "ip-address" to "192.0.0.1"
+            )
+            assertEquals(assignmentListForRequest1.size, assignmentList.size)
+            assertEquals(assignmentListForRequest2.size, assignmentList.size)
+
+            // firmware-version has max-occurrence = 0 means perform new resolution all the time.
+            // ip-address has max-occurrence = 1 so its resolution should only be done once.
+            //
+            // AlwaysPerformNewResolution + max-occurrence feature use case - resolution request #2 should returns
+            // the resolution as per assignmentListForRequest2 since new resolution is only performed for
+            // firmware-version and not for ip-address.
+            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.
      */
index 3ca754f..e247ea2 100644 (file)
         "action-name": "assign-activate",
         "scope-type": "vnf-type",
         "hostname": "localhost",
-        "vnf_name": "temp_vnf"
+        "vnf_name": "temp_vnf",
+        "firmware-version": "firmware-version-0",
+        "ip-address": "192.0.0.1"
+
       }
     }
   }
index 7264775..17f5aad 100644 (file)
@@ -24,7 +24,9 @@
         "action-name": "assign-activate",
         "scope-type": "vnf-type",
         "hostname": "localhost",
-        "vnf_name": "temp_vnf_new_resolution"
+        "vnf_name": "temp_vnf_new_resolution",
+        "firmware-version": "firmware-version-1",
+        "ip-address": "192.0.0.2"
       }
     }
   }