Extend API: Get Module Definitions 85/137085/8
authorhalil.cakal <halil.cakal@est.tech>
Tue, 23 Jan 2024 10:05:36 +0000 (10:05 +0000)
committerhalil.cakal <halil.cakal@est.tech>
Thu, 1 Feb 2024 16:41:39 +0000 (16:41 +0000)
- add query parameters: module-name and revision to OpenAPI
- extend the controller method to hande the new parameters
- add the new method stack to the service layer
- extend the SQL query to support the new parameters
- add unit and integration testwares

Issue-ID: CPS-1135
Change-Id: I089ad2ad71effb58ac0ba809e9f441d6cdb59c4f
Signed-off-by: halil.cakal <halil.cakal@est.tech>
19 files changed:
cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java
cps-ncmp-rest/docs/openapi/components.yaml
cps-ncmp-rest/docs/openapi/ncmp.yml
cps-ncmp-rest/docs/openapi/openapi.yml
cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java
cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/NetworkCmProxyDataService.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImpl.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistence.java
cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplSpec.groovy
cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy
cps-ri/src/main/java/org/onap/cps/spi/impl/CpsModulePersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/spi/repository/YangResourceRepository.java
cps-service/src/main/java/org/onap/cps/api/CpsModuleService.java
cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java
cps-service/src/main/java/org/onap/cps/spi/CpsModulePersistenceService.java
cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy

index e33af45..0e4f7f9 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2022 Bell Canada
- *  Modifications Copyright (c) 2022-2023 Nordix Foundation
+ *  Modifications Copyright (c) 2022-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -167,7 +167,9 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi {
     }
 
     @Override
-    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitionsByCmHandleId(final String cmHandle) {
+    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitions(final String cmHandleId,
+                                                                           final String moduleName,
+                                                                           final String revision) {
         return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
     }
 
index 3bee633..6b53292 100644 (file)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2023 Nordix Foundation
+#  Copyright (C) 2021-2024 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2022 Bell Canada
 #  ================================================================================
@@ -492,6 +492,22 @@ components:
       schema:
         type: string
         example: my-cm-handle
+    moduleNameInQuery:
+      name: module-name
+      in: query
+      description: Filter for a module name.This is an optional parameter
+      required: false
+      schema:
+        type: string
+        example: my-module
+    revisionInQuery:
+      name: revision
+      in: query
+      description: Filter for a module revision.This is an optional parameter and ignored when no module name is supplied
+      required: false
+      schema:
+        type: string
+        example: 2024-01-22
     dataSyncEnabled:
       name: dataSyncEnabled
       in: query
index 95ca6cc..2f90155 100755 (executable)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2023 Nordix Foundation
+#  Copyright (C) 2021-2024 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2021-2022 Bell Canada
 #  ================================================================================
@@ -289,15 +289,17 @@ fetchModuleReferencesByCmHandle:
       500:
         $ref: 'components.yaml#/components/responses/InternalServerError'
 
-fetchModuleDefinitionsByCmHandle:
+getModuleDefinitions:
   get:
-    description: Fetch all module definitions (name, revision, yang resource) for a given cm handle
     tags:
       - network-cm-proxy
-    summary: Fetch all module definitions (name, revision, yang resource) for a given cm handle
-    operationId: getModuleDefinitionsByCmHandleId
+    summary: Get module definitions
+    description: Get module definitions (module name, revision, yang resource) with options to filter on module name and revision
+    operationId: getModuleDefinitions
     parameters:
       - $ref: 'components.yaml#/components/parameters/cmHandleInPath'
+      - $ref: 'components.yaml#/components/parameters/moduleNameInQuery'
+      - $ref: 'components.yaml#/components/parameters/revisionInQuery'
     responses:
       200:
         description: OK
index 7ceb4fe..dd6d7c8 100755 (executable)
@@ -1,5 +1,5 @@
 #  ============LICENSE_START=======================================================
-#  Copyright (C) 2021-2023 Nordix Foundation
+#  Copyright (C) 2021-2024 Nordix Foundation
 #  Modifications Copyright (C) 2021 Pantheon.tech
 #  Modifications Copyright (C) 2021 Bell Canada
 #  ================================================================================
@@ -44,7 +44,7 @@ paths:
     $ref: 'ncmp.yml#/fetchModuleReferencesByCmHandle'
 
   /v1/ch/{cm-handle}/modules/definitions:
-    $ref: 'ncmp.yml#/fetchModuleDefinitionsByCmHandle'
+    $ref: 'ncmp.yml#/getModuleDefinitions'
 
   /v1/ch/searches:
     $ref: 'ncmp.yml#/searchCmHandles'
index be33c6a..6ec2444 100755 (executable)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
- *  Modifications Copyright (C) 2021-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 highstreet technologies GmbH
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  ================================================================================
@@ -31,6 +31,7 @@ import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH;
 import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE;
 
 import io.micrometer.core.annotation.Timed;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -59,9 +60,11 @@ import org.onap.cps.ncmp.rest.model.RestOutputCmHandle;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandleCompositeState;
 import org.onap.cps.ncmp.rest.model.RestOutputCmHandlePublicProperties;
 import org.onap.cps.ncmp.rest.util.DeprecationHelper;
+import org.onap.cps.spi.model.ModuleDefinition;
 import org.onap.cps.utils.JsonObjectMapper;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
+import org.springframework.util.StringUtils;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
@@ -317,18 +320,32 @@ public class NetworkCmProxyController implements NetworkCmProxyApi {
     }
 
     /**
-     * Return module definitions for a cm handle.
+     * Return module definitions.
      *
-     * @param cmHandleId cm-handle identifier
+     * @param cmHandleId    cm-handle identifier
+     * @param moduleName    module name
+     * @param revision      the revision of the module
      * @return list of module definitions (module name, revision, yang resource content)
      */
     @Override
-    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitionsByCmHandleId(final String cmHandleId) {
-        final List<RestModuleDefinition> restModuleDefinitions =
-                networkCmProxyDataService.getModuleDefinitionsByCmHandleId(cmHandleId).stream()
-                        .map(ncmpRestInputMapper::toRestModuleDefinition)
-                        .collect(Collectors.toList());
-        return new ResponseEntity<>(restModuleDefinitions, HttpStatus.OK);
+    public ResponseEntity<List<RestModuleDefinition>> getModuleDefinitions(final String cmHandleId,
+                                                                           final String moduleName,
+                                                                           final String revision) {
+        final Collection<ModuleDefinition> moduleDefinitions;
+        if (StringUtils.hasText(moduleName)) {
+            moduleDefinitions =
+                networkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, revision);
+        } else {
+            moduleDefinitions = networkCmProxyDataService.getModuleDefinitionsByCmHandleId(cmHandleId);
+            if (StringUtils.hasText(revision)) {
+                log.warn("Ignoring revision filter as no module name is provided");
+            }
+        }
+        final List<RestModuleDefinition> response = new ArrayList<>();
+        for (final ModuleDefinition moduleDefinition: moduleDefinitions) {
+            response.add(ncmpRestInputMapper.toRestModuleDefinition(moduleDefinition));
+        }
+        return new ResponseEntity<>(response, HttpStatus.OK);
     }
 
     /**
index 314a0b2..983f243 100644 (file)
@@ -2,7 +2,7 @@
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021-2022 Bell Canada.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
 
 package org.onap.cps.ncmp.rest.controller
 
-import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
-import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
-import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
-import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
-import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
-import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
-
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.read.ListAppender
 import com.fasterxml.jackson.databind.ObjectMapper
 import org.mapstruct.factory.Mappers
 import org.onap.cps.TestUtils
+import org.onap.cps.events.EventsPublisher
 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
 import org.onap.cps.ncmp.api.NetworkCmProxyQueryService
 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
@@ -50,19 +38,20 @@ import org.onap.cps.ncmp.api.impl.inventory.CompositeState
 import org.onap.cps.ncmp.api.impl.inventory.DataStoreSyncState
 import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
 import org.onap.cps.ncmp.api.impl.trustlevel.TrustLevel
-import org.onap.cps.ncmp.rest.model.DataOperationRequest
-import org.onap.cps.ncmp.rest.model.DataOperationDefinition
 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpCachedResourceRequestHandler
 import org.onap.cps.ncmp.rest.controller.handlers.NcmpPassthroughResourceRequestHandler
 import org.onap.cps.ncmp.rest.executor.CpsNcmpTaskExecutor
 import org.onap.cps.ncmp.rest.mapper.CmHandleStateMapper
 import org.onap.cps.ncmp.rest.mapper.DataOperationRequestMapper
+import org.onap.cps.ncmp.rest.model.DataOperationDefinition
+import org.onap.cps.ncmp.rest.model.DataOperationRequest
 import org.onap.cps.ncmp.rest.util.DeprecationHelper
 import org.onap.cps.spi.FetchDescendantsOption
 import org.onap.cps.spi.model.ModuleDefinition
 import org.onap.cps.spi.model.ModuleReference
 import org.onap.cps.utils.JsonObjectMapper
+import org.slf4j.LoggerFactory
 import org.spockframework.spring.SpringBean
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -72,10 +61,28 @@ import org.springframework.http.MediaType
 import org.springframework.test.web.servlet.MockMvc
 import spock.lang.Shared
 import spock.lang.Specification
+
 import java.time.OffsetDateTime
 import java.time.ZoneOffset
 import java.time.format.DateTimeFormatter
 
+import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.DataStores
+import static org.onap.cps.ncmp.api.impl.inventory.CompositeState.Operational
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_OPERATIONAL
+import static org.onap.cps.ncmp.api.impl.operations.DatastoreType.PASSTHROUGH_RUNNING
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.CREATE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.DELETE
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.PATCH
+import static org.onap.cps.ncmp.api.impl.operations.OperationType.UPDATE
+import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS
+import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put
+
 @WebMvcTest(NetworkCmProxyController)
 class NetworkCmProxyControllerSpec extends Specification {
 
@@ -130,11 +137,18 @@ class NetworkCmProxyControllerSpec extends Specification {
     def NO_REQUEST_ID = null
     def TIMOUT_FOR_TEST = 1234
 
+    def logger = Spy(ListAppender<ILoggingEvent>)
+
     def setup() {
         ncmpCachedResourceRequestHandler.notificationFeatureEnabled = true
         ncmpCachedResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
         ncmpPassthroughResourceRequestHandler.notificationFeatureEnabled = true
         ncmpPassthroughResourceRequestHandler.timeOutInMilliSeconds = TIMOUT_FOR_TEST
+        setupLogger()
+    }
+
+    def cleanup() {
+        ((Logger) LoggerFactory.getLogger(EventsPublisher.class)).detachAndStopAllAppenders()
     }
 
     def 'Get Resource Data from pass-through operational.'() {
@@ -516,21 +530,46 @@ class NetworkCmProxyControllerSpec extends Specification {
             ':passthrough-running'     | 'passthrough-running'
     }
 
-    def 'Get module definitions based on cmHandleId.'() {
-        when: 'get module definition request is performed'
+    def 'Getting module definitions for a module'() {
+        when: 'get module definition request is performed with module name'
             def response = mvc.perform(
-                    get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions"))
-                    .andReturn().response
-        then: 'ncmp service method to get module definitions is called'
-            mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle')
-                    >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
-                    'module sampleModuleName{ sample module content }')]
+                get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=sampleModuleName"))
+                .andReturn().response
+        then: 'ncmp service method is invoked with correct parameters'
+            mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', 'sampleModuleName', _)
+                >> [new ModuleDefinition('sampleModuleName', '2021-10-03',
+                'module sampleModuleName{ sample module content }')]
         and: 'response contains an array with the module name, revision and content'
             response.getContentAsString() == '[{"moduleName":"sampleModuleName","revision":"2021-10-03","content":"module sampleModuleName{ sample module content }"}]'
         and: 'response returns an OK http code'
             response.status == HttpStatus.OK.value()
     }
 
+    def 'Getting module definitions filtering on #scenario'() {
+        when: 'get module definition request is performed'
+            def response = mvc.perform(
+                get("$ncmpBasePathV1/ch/some-cmhandle/modules/definitions?module-name=" + moduleName + "&revision=" + revision))
+                .andReturn().response
+        then: 'ncmp service method to get definitions by cm handle is invoked when needed'
+            numberOfCallsToByCmHandleId * mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleId('some-cmhandle') >> []
+        and: 'ncmp service method to get definitions by module is invoked when needed'
+            numberOfCallsToByModule * mockNetworkCmProxyDataService.getModuleDefinitionsByCmHandleAndModule('some-cmhandle', moduleName, revision) >> []
+        and: 'response returns an OK http code'
+            response.status == HttpStatus.OK.value()
+        and: 'the correct message is logged when needed'
+            if (expectLogWarning) {
+                def lastLoggingEvent = logger.list[0]
+                assert lastLoggingEvent.level == Level.WARN
+                assert lastLoggingEvent.formattedMessage.contains('Ignoring revision')
+            }
+        where: 'following parameters are used'
+            scenario                   | moduleName    | revision        || numberOfCallsToByCmHandleId | numberOfCallsToByModule | expectLogWarning
+            'module name'              | 'some-module' | ''              || 0                           | 1                       | false
+            'module name and revision' | 'some-module' | 'some-revision' || 0                           | 1                       | false
+            'no filtering'             | ''            | ''              || 1                           | 0                       | false
+            'only revision'            | ''            | 'some-revision' || 1                           | 0                       | true
+    }
+
     def 'Set the data sync enabled based on the cm handle id and the data sync flag is #scenario'() {
         when: 'the set data sync enabled request is invoked'
             def response = mvc.perform(
@@ -667,5 +706,12 @@ class NetworkCmProxyControllerSpec extends Specification {
         return dataOperationDefinition
     }
 
+    def setupLogger() {
+        def setupLogger = ((Logger) LoggerFactory.getLogger(NetworkCmProxyController.class))
+        setupLogger.setLevel(Level.DEBUG)
+        setupLogger.addAppender(logger)
+        logger.start()
+    }
+
 }
 
index 0c12875..0c84748 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  *  ================================================================================
@@ -125,6 +125,18 @@ public interface NetworkCmProxyDataService {
      */
     Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId);
 
+    /**
+     * Get module definitions for the given parameters.
+     *
+     * @param cmHandleId        cm-handle identifier
+     * @param moduleName        module name
+     * @param moduleRevision    the revision of the module
+     * @return list of module definitions (module name, revision, yang resource content)
+     */
+    Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(String cmHandleId,
+                                                                         String moduleName,
+                                                                         String moduleRevision);
+
     /**
      * Query cm handle details by cm handle's name.
      *
index 469d75a..1e7edd4 100755 (executable)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2021 highstreet technologies GmbH
- *  Modifications Copyright (C) 2021-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
@@ -188,6 +188,13 @@ public class NetworkCmProxyDataServiceImpl implements NetworkCmProxyDataService
         return inventoryPersistence.getModuleDefinitionsByCmHandleId(cmHandleId);
     }
 
+    @Override
+    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleId,
+                                                                                final String moduleName,
+                                                                                final String moduleRevision) {
+        return inventoryPersistence.getModuleDefinitionsByCmHandleAndModule(cmHandleId, moduleName, moduleRevision);
+    }
+
     /**
      * Retrieve cm handles with details for the given query parameters.
      *
index 9024eac..dcd0368 100644 (file)
@@ -79,6 +79,18 @@ public interface InventoryPersistence extends NcmpPersistence {
      */
     Collection<ModuleDefinition> getModuleDefinitionsByCmHandleId(String cmHandleId);
 
+    /**
+     * Method to return module definitions for the given parameters.
+     *
+     * @param cmHandleId        cm-handle identifier
+     * @param moduleName        module name
+     * @param moduleRevision    the revision of the module
+     * @return list of module definitions (module name, revision, yang resource content)
+     */
+    Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(String cmHandleId,
+                                                                         String moduleName,
+                                                                         String moduleRevision);
+
     /**
      * Method to return module references by cmHandleId.
      *
index 33d6e9a..3b70786 100644 (file)
@@ -127,6 +127,15 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv
         return cpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId);
     }
 
+    @Override
+    public Collection<ModuleDefinition> getModuleDefinitionsByCmHandleAndModule(final String cmHandleId,
+                                                                                final String moduleName,
+                                                                                final String moduleRevision) {
+        cpsValidator.validateNameCharacters(cmHandleId, moduleName);
+        return cpsModuleService.getModuleDefinitionsByAnchorAndModule(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,
+                    cmHandleId, moduleName, moduleRevision);
+    }
+
     @Override
     public Collection<ModuleReference> getYangResourcesModuleReferences(final String cmHandleId) {
         cpsValidator.validateNameCharacters(cmHandleId);
index 4f7b726..4c8b2bb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation
+ *  Copyright (C) 2021-2024 Nordix Foundation
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2021-2022 Bell Canada
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
@@ -292,10 +292,17 @@ class NetworkCmProxyDataServiceImplSpec extends Specification {
             assert result == ['cm-handle-id-1']
     }
 
-    def 'Getting module definitions.'() {
-        when: 'get module definitions method is called with a valid cm handle ID'
+    def 'Getting module definitions by module'() {
+        when: 'get module definitions is performed with module name'
+            objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
+        then: 'ncmp inventory persistence service is invoked once with correct parameters'
+            1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleAndModule('some-cm-handle', 'some-module', '2021-08-04')
+    }
+
+    def 'Getting module definitions by cm handle id'() {
+        when: 'get module definitions is performed with cm handle id'
             objectUnderTest.getModuleDefinitionsByCmHandleId('some-cm-handle')
-        then: 'CPS module services is invoked once'
+        then: 'ncmp inventory persistence service is invoked once with correct parameter'
             1 * mockInventoryPersistence.getModuleDefinitionsByCmHandleId('some-cm-handle')
     }
 
index cb2f3fd..a3b923f 100644 (file)
@@ -184,11 +184,23 @@ class InventoryPersistenceImplSpec extends Specification {
             'DELETING'  | CmHandleState.DELETING || ['/dmi-registry/cm-handles[@id=\'Some-Cm-Handle1\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}', '/dmi-registry/cm-handles[@id=\'Some-Cm-Handle2\']':'{"state":{"cm-handle-state":"DELETING","last-update-time":"2022-12-31T20:30:40.000+0000"}}']
     }
 
-    def 'Get module definitions'() {
-        given: 'cps module service returns a collection of module definitions'
+    def 'Getting module definitions by module'() {
+        given: 'cps module service returns module definition for module name'
+            def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
+            mockCpsModuleService.getModuleDefinitionsByAnchorAndModule(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id', 'some-module', '2024-01-25') >> moduleDefinitions
+        when: 'get module definitions is invoked with module name'
+            def result = objectUnderTest.getModuleDefinitionsByCmHandleAndModule('some-cmHandle-Id', 'some-module', '2024-01-25')
+        then: 'returned result are the same module definitions as returned from module service'
+            assert result == moduleDefinitions
+        and: 'cm handle id and module name validated'
+            1 * mockCpsValidator.validateNameCharacters('some-cmHandle-Id', 'some-module')
+    }
+
+    def 'Getting module definitions with cm handle id'() {
+        given: 'cps module service returns module definitions for cm handle id'
             def moduleDefinitions = [new ModuleDefinition('moduleName','revision','content')]
             mockCpsModuleService.getModuleDefinitionsByAnchorName(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME,'some-cmHandle-Id') >> moduleDefinitions
-        when: 'get module definitions by cmHandle is invoked'
+        when: 'get module definitions is invoked with cm handle id'
             def result = objectUnderTest.getModuleDefinitionsByCmHandleId('some-cmHandle-Id')
         then: 'the returned result are the same module definitions as returned from the module service'
             assert result == moduleDefinitions
index b115f9a..1e42702 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2023 Nordix Foundation
+ *  Copyright (C) 2020-2024 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -32,6 +32,7 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -79,6 +80,8 @@ import org.springframework.stereotype.Component;
 public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceService {
 
     private static final String YANG_RESOURCE_CHECKSUM_CONSTRAINT_NAME = "yang_resource_checksum_key";
+    private static final String NO_MODULE_NAME_FILTER = null;
+    private static final String NO_MODULE_REVISION = null;
     private static final Pattern CHECKSUM_EXCEPTION_PATTERN = Pattern.compile(".*\\(checksum\\)=\\((\\w+)\\).*");
     private static final Pattern RFC6020_RECOMMENDED_FILENAME_PATTERN = Pattern
             .compile("([\\w-]+)@(\\d{4}-\\d{2}-\\d{2})(?:\\.yang)?", Pattern.CASE_INSENSITIVE);
@@ -122,10 +125,29 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
     public Collection<ModuleDefinition> getYangResourceDefinitions(final String dataspaceName,
                                                                    final String anchorName) {
         final Set<YangResourceEntity> yangResourceEntities =
-                yangResourceRepository
-                        .findAllModuleDefinitionsByDataspaceAndAnchor(dataspaceName, anchorName);
-        return yangResourceEntities.stream().map(CpsModulePersistenceServiceImpl::toModuleDefinition)
-                .collect(Collectors.toList());
+                yangResourceRepository.findAllModuleDefinitionsByDataspaceAndAnchorAndModule(dataspaceName, anchorName,
+                    NO_MODULE_NAME_FILTER, NO_MODULE_REVISION);
+        return convertYangResourceEntityToModuleDefinition(yangResourceEntities);
+    }
+
+    @Override
+    public Collection<ModuleDefinition> getYangResourceDefinitionsByAnchorAndModule(final String dataspaceName,
+                                                                                    final String anchorName,
+                                                                                    final String moduleName,
+                                                                                    final String moduleRevision) {
+        final Set<YangResourceEntity> yangResourceEntities =
+            yangResourceRepository.findAllModuleDefinitionsByDataspaceAndAnchorAndModule(dataspaceName, anchorName,
+                moduleName, moduleRevision);
+        return convertYangResourceEntityToModuleDefinition(yangResourceEntities);
+    }
+
+    private List<ModuleDefinition> convertYangResourceEntityToModuleDefinition(final Set<YangResourceEntity>
+                                                                                   yangResourceEntities) {
+        final List<ModuleDefinition> resultModuleDefinitions = new ArrayList<>(yangResourceEntities.size());
+        for (final YangResourceEntity yangResourceEntity: yangResourceEntities) {
+            resultModuleDefinitions.add(toModuleDefinition(yangResourceEntity));
+        }
+        return resultModuleDefinitions;
     }
 
     @Override
index 3fec462..b37f635 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  ============LICENSE_START=======================================================
  *  Copyright (C) 2020 Pantheon.tech
- *  Modifications Copyright (C) 2021-2023 Nordix Foundation
+ *  Modifications Copyright (C) 2021-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
  *  you may not use this file except in compliance with the License.
@@ -84,9 +84,12 @@ public interface YangResourceRepository extends JpaRepository<YangResourceEntity
             WHERE
                     dataspace.name = :dataspaceName
                 AND anchor.name = :anchorName
+                AND (:moduleName IS NULL OR yang_resource.module_name = :moduleName)
+                AND (:revision IS NULL OR yang_resource.revision = :revision)
             """, nativeQuery = true)
-    Set<YangResourceEntity> findAllModuleDefinitionsByDataspaceAndAnchor(
-            @Param("dataspaceName") String dataspaceName, @Param("anchorName") String anchorName);
+    Set<YangResourceEntity> findAllModuleDefinitionsByDataspaceAndAnchorAndModule(
+            @Param("dataspaceName") String dataspaceName, @Param("anchorName") String anchorName,
+            @Param("moduleName") String moduleName, @Param("revision") String revision);
 
     @Query(value = """
             SELECT DISTINCT
index 2928464..bdd3614 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2023 Nordix Foundation
+ *  Copyright (C) 2020-2024 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -133,6 +133,18 @@ public interface CpsModuleService {
      */
     Collection<ModuleDefinition> getModuleDefinitionsByAnchorName(String dataspaceName, String anchorName);
 
+    /**
+     * Retrieve module definitions for the given parameters.
+     *
+     * @param dataspaceName     dataspace name
+     * @param anchorName        anchor name
+     * @param moduleName        module name
+     * @param moduleRevision    the revision of the module
+     * @return a collection of module definitions (moduleName, revision, yang resource content)
+     */
+    Collection<ModuleDefinition> getModuleDefinitionsByAnchorAndModule(String dataspaceName, String anchorName,
+                                                                  String moduleName, String moduleRevision);
+
     /**
      * Identify previously unknown Yang Resource module references.
      * The system will ignore the namespace of all module references.
index 61a4e62..0f9eb3c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2023 Nordix Foundation
+ *  Copyright (C) 2020-2024 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2022 Bell Canada
  *  Modifications Copyright (C) 2022 TechMahindra Ltd
@@ -155,6 +155,15 @@ public class CpsModuleServiceImpl implements CpsModuleService {
         return cpsModulePersistenceService.getYangResourceDefinitions(dataspaceName, anchorName);
     }
 
+    @Override
+    public Collection<ModuleDefinition> getModuleDefinitionsByAnchorAndModule(final String dataspaceName,
+                                                                              final String anchorName,
+                                                                              final String moduleName,
+                                                                              final String moduleRevision) {
+        return cpsModulePersistenceService.getYangResourceDefinitionsByAnchorAndModule(dataspaceName, anchorName,
+            moduleName, moduleRevision);
+    }
+
     @Override
     public Collection<ModuleReference> identifyNewModuleReferences(
         final Collection<ModuleReference> moduleReferencesToCheck) {
index aaca2ee..eeaaa47 100755 (executable)
@@ -1,6 +1,6 @@
 /*
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2023 Nordix Foundation
+ *  Copyright (C) 2020-2024 Nordix Foundation
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
  *  ================================================================================
@@ -125,6 +125,18 @@ public interface CpsModulePersistenceService {
      */
     Collection<ModuleDefinition> getYangResourceDefinitions(String dataspaceName, String anchorName);
 
+    /**
+     * Get YANG resource definitions for the given parameters.
+     *
+     * @param dataspaceName  dataspace name
+     * @param anchorName     anchor name
+     * @param moduleName     module name
+     * @param moduleRevision the revision of the module
+     * @return a collection of module definitions (moduleName, revision and yang resource content)
+     */
+    Collection<ModuleDefinition> getYangResourceDefinitionsByAnchorAndModule(String dataspaceName, String anchorName,
+                                                                             String moduleName, String moduleRevision);
+
     /**
      * Remove unused Yang Resource Modules.
      */
index d909e27..0bad0de 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2023 Nordix Foundation
+ *  Copyright (C) 2020-2024 Nordix Foundation
  *  Modifications Copyright (C) 2020-2021 Pantheon.tech
  *  Modifications Copyright (C) 2020-2022 Bell Canada.
  *  Modifications Copyright (C) 2022 TechMahindra Ltd.
@@ -238,15 +238,25 @@ class CpsModuleServiceImplSpec extends Specification {
             1 * mockCpsModulePersistenceService.identifyNewModuleReferences(moduleReferencesToCheck)
     }
 
-    def 'Getting module definitions.'() {
-        given: 'the module persistence service returns a collection of module definitions'
+    def 'Getting module definitions with module name'() {
+        given: 'module persistence service returns module definitions for module name'
+            def moduleDefinitionsFromPersistenceService = [ new ModuleDefinition('name', 'revision', 'content' ) ]
+            mockCpsModulePersistenceService.getYangResourceDefinitionsByAnchorAndModule('some-dataspace-name', 'some-anchor-name', 'some-module', '2024-01-01')  >> moduleDefinitionsFromPersistenceService
+        when: 'get module definitions method is called with anchor and module name'
+            def result = objectUnderTest.getModuleDefinitionsByAnchorAndModule('some-dataspace-name', 'some-anchor-name', 'some-module', '2024-01-01')
+        then: 'the result is the same collection returned by the persistence service'
+            assert result == moduleDefinitionsFromPersistenceService
+    }
+
+    def 'Getting module definitions with anchor name'() {
+        given: 'the module persistence service returns module definitions for cm handle id'
             def moduleDefinitionsFromPersistenceService = [ new ModuleDefinition('name', 'revision', 'content' ) ]
             mockCpsModulePersistenceService.getYangResourceDefinitions('some-dataspace-name', 'some-anchor-name')  >> moduleDefinitionsFromPersistenceService
         when: 'get module definitions method is called with a valid dataspace and anchor name'
             def result = objectUnderTest.getModuleDefinitionsByAnchorName('some-dataspace-name', 'some-anchor-name')
         then: 'the result is the same collection returned by the persistence service'
             assert result == moduleDefinitionsFromPersistenceService
-        and: 'the CpsValidator is called on the dataspaceName and schemaSetName'
+        and: 'cps validator is called on the dataspace and anchor name'
             1 * mockCpsValidator.validateNameCharacters('some-dataspace-name', 'some-anchor-name')
     }
 
index cf0e0b5..8029ae0 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation
+ *  Copyright (C) 2023-2024 Nordix Foundation
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the 'License');
  *  you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
 package org.onap.cps.integration.functional
 
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.api.impl.CpsModuleServiceImpl
 import org.onap.cps.integration.base.FunctionalSpecBase
 import org.onap.cps.spi.CascadeDeleteAllowed
 import org.onap.cps.spi.exceptions.AlreadyDefinedException
@@ -153,6 +152,17 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase {
             result == [new ModuleDefinition('stores','2020-09-15','')]
     }
 
+    def 'Retrieving module definitions: #scenarios'() {
+        when: 'module definitions for module name are retrieved'
+            def result = objectUnderTest.getModuleDefinitionsByAnchorAndModule(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, moduleName, moduleRevision)
+        then: 'the correct module definitions are returned'
+            result == [new ModuleDefinition('stores','2020-09-15','')]
+        where: 'following parameters are used'
+            scenarios               | moduleName | moduleRevision
+            'both specified'        | 'stores'   | '2020-09-15'
+            'module name specified' | 'stores'   | null
+    }
+
     def 'Retrieving yang resource module references by anchor.'() {
         when: 'the yang resource module references for an anchor are retrieved'
             def result = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1)