Store yang resources with recommended RFC-6020 file-name 12/140212/2
authorToineSiebelink <toine.siebelink@est.tech>
Tue, 11 Feb 2025 18:44:48 +0000 (18:44 +0000)
committerToineSiebelink <toine.siebelink@est.tech>
Wed, 12 Feb 2025 15:50:35 +0000 (15:50 +0000)
- Ignore input filename and create filename from module name and revision
- added integration test to verify names and edge cases (before and after change)
- Some code cleanup (vars etc)
- Implemented NB comments from last merge(https://gerrit.onap.org/r/c/cps/+/140180)
- fixed SQ warning
Out of scope:
- BLANK revision, test it but failed in ODL Yang Parser and many other places: not supported!

Issue-ID: CPS-138
Change-Id: I6fe6d0f8f3683196b183c6e6582ad8eefdfbb7d7
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
cps-ri/src/main/java/org/onap/cps/ri/CpsModulePersistenceServiceImpl.java
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentQueryBuilder.java
cps-ri/src/main/java/org/onap/cps/ri/repository/FragmentRepositoryCpsPathQuery.java
cps-service/src/main/java/org/onap/cps/api/CpsQueryService.java
cps-service/src/main/java/org/onap/cps/impl/CpsQueryServiceImpl.java
cps-service/src/main/java/org/onap/cps/yang/YangTextSchemaSourceSetBuilder.java
integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy
integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy

index dbc6c28..412c6f9 100755 (executable)
@@ -24,6 +24,7 @@
 package org.onap.cps.ri;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.opendaylight.yangtools.yang.common.YangConstants.RFC6020_YANG_FILE_EXTENSION;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableSet;
@@ -253,25 +254,24 @@ public class CpsModulePersistenceServiceImpl implements CpsModulePersistenceServ
 
     private static Map<String, YangResourceEntity> getYangResourceEntityPerChecksum(
         final Map<String, String> yangResourceContentPerName) {
-        final Map<String, YangResourceEntity> yangResourceEntityPerChecksum =
-            yangResourceContentPerName.entrySet().stream()
-            .map(entry -> {
-                final String checksum = DigestUtils.sha256Hex(entry.getValue().getBytes(StandardCharsets.UTF_8));
-                final Map<String, String> moduleNameAndRevisionMap = createModuleNameAndRevisionMap(entry.getKey(),
-                            entry.getValue());
-                final YangResourceEntity yangResourceEntity = new YangResourceEntity();
-                yangResourceEntity.setFileName(entry.getKey());
-                yangResourceEntity.setContent(entry.getValue());
-                yangResourceEntity.setModuleName(moduleNameAndRevisionMap.get("moduleName"));
-                yangResourceEntity.setRevision(moduleNameAndRevisionMap.get("revision"));
-                yangResourceEntity.setChecksum(checksum);
-                return yangResourceEntity;
-            })
-            .collect(Collectors.toMap(
-                YangResourceEntity::getChecksum,
-                entity -> entity
-            ));
-        return yangResourceEntityPerChecksum;
+        return yangResourceContentPerName.entrySet().stream().map(entry -> {
+            final String checksum = DigestUtils.sha256Hex(entry.getValue().getBytes(StandardCharsets.UTF_8));
+            final Map<String, String> moduleNameAndRevisionMap = createModuleNameAndRevisionMap(entry.getKey(),
+                        entry.getValue());
+            final YangResourceEntity yangResourceEntity = new YangResourceEntity();
+            yangResourceEntity.setContent(entry.getValue());
+            final String moduleName = moduleNameAndRevisionMap.get("moduleName");
+            final String revision = moduleNameAndRevisionMap.get("revision");
+            yangResourceEntity.setModuleName(moduleName);
+            yangResourceEntity.setRevision(revision);
+            yangResourceEntity.setFileName(moduleName + "@" + revision + RFC6020_YANG_FILE_EXTENSION);
+            yangResourceEntity.setChecksum(checksum);
+            return yangResourceEntity;
+        })
+    .collect(Collectors.toMap(
+        YangResourceEntity::getChecksum,
+        entity -> entity
+    ));
     }
 
     private void createAndSaveSchemaSetEntity(final String dataspaceName,
index bf354be..5563ba6 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2022-2024 Nordix Foundation
+ *  Copyright (C) 2022-2025 Nordix Foundation
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
index 4ee6555..50c7494 100644 (file)
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation.
+ *  Copyright (C) 2021-2025 Nordix Foundation.
  *  Modifications Copyright (C) 2023 TechMahindra Ltd.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
index 3044fe0..30c8bbb 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  ============LICENSE_START=======================================================
- *  Copyright (C) 2020-2024 Nordix Foundation
+ *  Copyright (C) 2020-2025 Nordix Foundation
  *  Modifications Copyright (C) 2022-2023 TechMahindra Ltd.
  *  ================================================================================
  *  Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,7 +32,7 @@ import org.onap.cps.api.parameters.PaginationOption;
  */
 public interface CpsQueryService {
 
-    public static int NO_LIMIT = 0;
+    int NO_LIMIT = 0;
 
     /**
      * Get data nodes for the given dataspace and anchor by cps path.
index f27445f..a388482 100644 (file)
@@ -52,12 +52,12 @@ public class CpsQueryServiceImpl implements CpsQueryService {
     @Override
     @Timed(value = "cps.data.service.datanode.query",
             description = "Time taken to query data nodes with a limit on results")
-    public Collection<DataNode> queryDataNodes(final String dataSpaceName, final String anchorName,
+    public Collection<DataNode> queryDataNodes(final String dataspaceName, final String anchorName,
                                                final String cpsPath,
                                                final FetchDescendantsOption fetchDescendantsOption,
                                                final int queryResultLimit) {
-        cpsValidator.validateNameCharacters(dataSpaceName, anchorName);
-        return cpsDataPersistenceService.queryDataNodes(dataSpaceName,
+        cpsValidator.validateNameCharacters(dataspaceName, anchorName);
+        return cpsDataPersistenceService.queryDataNodes(dataspaceName,
                                                         anchorName,
                                                         cpsPath,
                                                         fetchDescendantsOption,
index ab7a095..04b4916 100644 (file)
@@ -33,6 +33,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import lombok.NoArgsConstructor;
@@ -77,7 +78,7 @@ public final class YangTextSchemaSourceSetBuilder {
      * @return the YangTextSchemaSourceSet
      */
     public YangTextSchemaSourceSet build() {
-        final var schemaContext = generateSchemaContext(yangModelMap.build());
+        final SchemaContext schemaContext = generateSchemaContext(yangModelMap.build());
         return new YangTextSchemaSourceSetImpl(schemaContext);
     }
 
@@ -113,9 +114,7 @@ public final class YangTextSchemaSourceSetBuilder {
 
         @Override
         public List<ModuleReference> getModuleReferences() {
-            return schemaContext.getModules().stream()
-                .map(YangTextSchemaSourceSetImpl::toModuleReference)
-                .collect(Collectors.toList());
+            return schemaContext.getModules().stream().map(YangTextSchemaSourceSetImpl::toModuleReference).toList();
         }
 
         private static ModuleReference toModuleReference(final Module module) {
@@ -164,12 +163,11 @@ public final class YangTextSchemaSourceSetBuilder {
 
     private static List<YangTextSchemaSource> forResources(final Map<String, String> yangResourceNameToContent) {
         return yangResourceNameToContent.entrySet().stream()
-            .map(entry -> toYangTextSchemaSource(entry.getKey(), entry.getValue()))
-            .collect(Collectors.toList());
+            .map(entry -> toYangTextSchemaSource(entry.getKey(), entry.getValue())).toList();
     }
 
     private static YangTextSchemaSource toYangTextSchemaSource(final String sourceName, final String source) {
-        final var revisionSourceIdentifier =
+        final RevisionSourceIdentifier revisionSourceIdentifier =
             createIdentifierFromSourceName(checkNotNull(sourceName));
 
         return new YangTextSchemaSource(revisionSourceIdentifier) {
@@ -192,7 +190,7 @@ public final class YangTextSchemaSourceSetBuilder {
     }
 
     private static RevisionSourceIdentifier createIdentifierFromSourceName(final String sourceName) {
-        final var matcher = RFC6020_RECOMMENDED_FILENAME_PATTERN.matcher(sourceName);
+        final Matcher matcher = RFC6020_RECOMMENDED_FILENAME_PATTERN.matcher(sourceName);
         if (matcher.matches()) {
             return RevisionSourceIdentifier.create(matcher.group(1), Revision.of(matcher.group(2)));
         }
index 453dbca..693cf99 100644 (file)
@@ -48,6 +48,7 @@ import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
 import org.onap.cps.ri.repository.DataspaceRepository
 import org.onap.cps.ri.repository.SchemaSetRepository
 import org.onap.cps.ri.utils.SessionManager
+import org.onap.cps.spi.CpsModulePersistenceService
 import org.onap.cps.utils.JsonObjectMapper
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.beans.factory.annotation.Value
@@ -100,6 +101,9 @@ abstract class CpsIntegrationSpecBase extends Specification {
     @Autowired
     SessionManager sessionManager
 
+    @Autowired
+    CpsModulePersistenceService cpsModulePersistenceService
+
     @Autowired
     DataspaceRepository dataspaceRepository
 
index 49201e8..9a48dd7 100644 (file)
@@ -21,8 +21,6 @@
 package org.onap.cps.integration.functional.cps
 
 import org.onap.cps.api.CpsModuleService
-import org.onap.cps.integration.base.FunctionalSpecBase
-import org.onap.cps.api.parameters.CascadeDeleteAllowed
 import org.onap.cps.api.exceptions.AlreadyDefinedException
 import org.onap.cps.api.exceptions.DataspaceNotFoundException
 import org.onap.cps.api.exceptions.ModelValidationException
@@ -30,6 +28,8 @@ import org.onap.cps.api.exceptions.SchemaSetInUseException
 import org.onap.cps.api.exceptions.SchemaSetNotFoundException
 import org.onap.cps.api.model.ModuleDefinition
 import org.onap.cps.api.model.ModuleReference
+import org.onap.cps.api.parameters.CascadeDeleteAllowed
+import org.onap.cps.integration.base.FunctionalSpecBase
 
 class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
 
@@ -132,7 +132,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
             objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema2', yangResourceContentPerName)
         then: 'the dataspace has no additional module (reference)'
             assert numberOfModuleReferencesAfterFirstSchemaSetHasBeenAdded  == objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).size()
-        cleanup:
+        cleanup: 'the data created in this test'
             objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchema1', 'newSchema2'])
     }
 
@@ -216,6 +216,9 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
             assert result.name == 'bookstoreSchemaSet'
             assert result.moduleReferences.size() == 2
             assert result.moduleReferences.containsAll(bookStoreModuleReferenceWithNamespace, bookStoreTypesModuleReferenceWithNamespace)
+        and: 'the yang resource is stored with the normalized filename'
+            def fileName = cpsModulePersistenceService.getYangSchemaResources(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET).keySet()[0]
+            assert fileName == 'bookstore-types@2024-01-30.yang'
     }
 
     def 'Retrieve all schema sets.'() {
@@ -227,10 +230,49 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
         then: 'the result contains all expected schema sets'
             assert result.name.size() == 2
             assert result.name.containsAll('bookstoreSchemaSet', 'newSchema1')
-        cleanup:
+        cleanup: 'the data created in this test'
             objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchema1'])
     }
 
+    def 'Create schema set with duplicate module filename [CPS-138].'() {
+        given: 'store the original number of sets and modules'
+            def numberOfSchemaSets = objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1).size()
+            def numberOfModuleReferences = objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).size()
+        and: 'create a new schema set using a module with filename identical to a previously stored module (e.g. bookstore)'
+            populateYangResourceContentPerNameAndAllModuleReferences('otherModule', 1)
+            def otherModuleContent = yangResourceContentPerName.values()[0]
+            def mapWithDuplicateName = ['bookstore' : otherModuleContent]
+            objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema', mapWithDuplicateName)
+        when: 'the yang resource details are retrieved'
+            def yangSchemaResources = cpsModulePersistenceService.getYangSchemaResources(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema')
+        then: 'the file name of the resource has been normalized'
+            def fileName = yangSchemaResources.keySet()[0]
+            assert fileName == 'otherModule_0@2000-01-01.yang'
+        and: 'the yang resource has the correct content'
+            assert yangSchemaResources.get(fileName) == otherModuleContent
+        and: 'the number of schema sets and modules has increased as expected'
+            assert objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1).size() == numberOfSchemaSets + 1
+            assert objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).size() == numberOfModuleReferences + 1
+        cleanup: 'the data created in this test'
+            objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchema'])
+    }
+
+    def 'Create schema set with RFC-6020 filename pattern but incorrect details [CPS-138].'() {
+        given: 'create a new schema set using a module with filename identical to a previously stored module (e.g. bookstore)'
+            populateYangResourceContentPerNameAndAllModuleReferences('otherModule', 1)
+            def otherModuleContent = yangResourceContentPerName.values()[0]
+            def mapIncorrectName = ['wrongModuleAndRevision@1999-08-08.yang': otherModuleContent]
+            objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema', mapIncorrectName)
+        when: 'the yang resource details are retrieved'
+            def yangSchemaResources = cpsModulePersistenceService.getYangSchemaResources(FUNCTIONAL_TEST_DATASPACE_1, 'newSchema')
+        then: 'the file name of the resource has been normalized'
+            def fileName = yangSchemaResources.keySet()[0]
+            assert fileName == 'otherModule_0@2000-01-01.yang'
+        cleanup: 'the data created in this test'
+            objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchema'])
+    }
+
+
     /*
         D E L E T E   S C H E M A   S E T   U S E - C A S E S
      */
@@ -253,7 +295,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
         then: 'check if the dataspace still contains the new schema set or not'
             def remainingSchemaSetNames = objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1).name
             assert remainingSchemaSetNames.contains('newSchemaSet') == expectSchemaSetStillPresent
-        cleanup:
+        cleanup: 'the data created in this test'
             objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchemaSet'])
         where: 'the following options are used'
             associateWithAnchor | cascadeDeleteAllowedOption                     || expectSchemaSetStillPresent
@@ -284,7 +326,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
             def remainingModuleRevisions = objectUnderTest.getYangResourceModuleReferences(FUNCTIONAL_TEST_DATASPACE_1).revision
             assert remainingModuleRevisions.contains('2000-01-01')
             assert !remainingModuleRevisions.contains('2001-01-01')
-        cleanup:
+        cleanup: 'the data created in this test'
             objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchemaSet1'])
     }
 
@@ -325,7 +367,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
             assert yangResourceModuleReferencesAfterUpgrade.size() == 3
             assert yangResourceModuleReferencesAfterUpgrade.contains(bookStoreModuleReference)
             assert yangResourceModuleReferencesAfterUpgrade.containsAll(newModuleReferences);
-        cleanup:
+        cleanup: 'the data created in this test'
             objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['targetSchema'])
     }
 
@@ -351,7 +393,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase {
         and: 'the associated target anchor has the same module references (without namespace but that is a legacy issue)'
             def anchorModuleReferencesAfterUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor')
             assert anchorModuleReferencesAfterUpgrade.containsAll([new ModuleReference('source_0','2000-01-01'),new ModuleReference('source_1','2001-01-01')]);
-        cleanup:
+        cleanup: 'the data created in this test'
             objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['sourceSchema', 'targetSchema'])
     }