diff options
11 files changed, 401 insertions, 126 deletions
diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java index d1145fd24a..d5238ae06b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoader.java @@ -30,6 +30,7 @@ import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDataspaceService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.api.exceptions.AlreadyDefinedException; +import org.onap.cps.init.AbstractModelLoader; import org.onap.cps.ncmp.api.data.models.DatastoreType; import org.onap.cps.ncmp.exceptions.NcmpStartUpException; import org.springframework.stereotype.Service; diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java index 76d12f290c..58a5f553af 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/InventoryModelLoader.java @@ -29,6 +29,7 @@ import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDataspaceService; import org.onap.cps.api.CpsModuleService; +import org.onap.cps.init.AbstractModelLoader; import org.springframework.stereotype.Service; @Slf4j diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy index 21f5e98e10..926d00041f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/CmDataSubscriptionModelLoaderSpec.groovy @@ -52,7 +52,7 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { def loggingListAppender void setup() { - expectedYangResourcesToContentMap = objectUnderTest.createYangResourcesToContentMap('cm-data-subscriptions@2024-02-12.yang') + expectedYangResourcesToContentMap = objectUnderTest.mapYangResourcesToContent('cm-data-subscriptions@2024-02-12.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy index b37cfbaab5..ffd1d89fe1 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/InventoryModelLoaderSpec.groovy @@ -51,7 +51,7 @@ class InventoryModelLoaderSpec extends Specification { def loggingListAppender void setup() { - expectedYangResourceToContentMap = objectUnderTest.createYangResourcesToContentMap('dmi-registry@2024-02-23.yang') + expectedYangResourceToContentMap = objectUnderTest.mapYangResourcesToContent('dmi-registry@2024-02-23.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) diff --git a/cps-service/src/main/java/org/onap/cps/api/exceptions/ModelOnboardingException.java b/cps-service/src/main/java/org/onap/cps/api/exceptions/ModelOnboardingException.java new file mode 100644 index 0000000000..d4455ebc20 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/api/exceptions/ModelOnboardingException.java @@ -0,0 +1,28 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.api.exceptions; + +public class ModelOnboardingException extends CpsException { + + public ModelOnboardingException(final String message, final String details) { + super(message, details); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java b/cps-service/src/main/java/org/onap/cps/init/AbstractModelLoader.java index ad5e431944..e864633f25 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java +++ b/cps-service/src/main/java/org/onap/cps/init/AbstractModelLoader.java @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2023-2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.init; +package org.onap.cps.init; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.InputStream; @@ -26,7 +27,6 @@ import java.nio.charset.StandardCharsets; import java.time.OffsetDateTime; import java.util.HashMap; import java.util.Map; -import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.api.CpsAnchorService; @@ -34,88 +34,93 @@ import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDataspaceService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.api.exceptions.AlreadyDefinedException; +import org.onap.cps.api.exceptions.ModelOnboardingException; import org.onap.cps.api.parameters.CascadeDeleteAllowed; -import org.onap.cps.ncmp.exceptions.NcmpStartUpException; import org.onap.cps.utils.JsonObjectMapper; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationStartedEvent; @Slf4j @RequiredArgsConstructor -abstract class AbstractModelLoader implements ModelLoader { +public abstract class AbstractModelLoader implements ModelLoader { - private final CpsDataspaceService cpsDataspaceService; + protected final CpsDataspaceService cpsDataspaceService; private final CpsModuleService cpsModuleService; private final CpsAnchorService cpsAnchorService; protected final CpsDataService cpsDataService; - private static final int EXIT_CODE_ON_ERROR = 1; - private final JsonObjectMapper jsonObjectMapper = new JsonObjectMapper(new ObjectMapper()); - @Value("${ncmp.model-loader.maximum-attempt-count:20}") - int maximumAttemptCount; - - @Value("${ncmp.timers.model-loader.retry-time-ms:1000}") - long retryTimeMs; + private static final int EXIT_CODE_ON_ERROR = 1; @Override - public void onApplicationEvent(@NonNull final ApplicationStartedEvent applicationStartedEvent) { + public void onApplicationEvent(final ApplicationStartedEvent applicationStartedEvent) { try { onboardOrUpgradeModel(); - } catch (final NcmpStartUpException ncmpStartUpException) { - log.error("Onboarding model for NCMP failed: {} ", ncmpStartUpException.getMessage()); + } catch (final Exception modelOnboardUpException) { + log.error("Exiting application due to failure in onboarding model: {} ", + modelOnboardUpException.getMessage()); SpringApplication.exit(applicationStartedEvent.getApplicationContext(), () -> EXIT_CODE_ON_ERROR); } } - void createSchemaSet(final String dataspaceName, final String schemaSetName, final String... resourceNames) { + /** + * Create initial schema set. + * @param dataspaceName dataspace name + * @param schemaSetName schemaset name + * @param resourceNames resource names + */ + public void createSchemaSet(final String dataspaceName, final String schemaSetName, final String... resourceNames) { try { - final Map<String, String> yangResourcesContentMap = createYangResourcesToContentMap(resourceNames); - cpsModuleService.createSchemaSet(dataspaceName, schemaSetName, yangResourcesContentMap); + final Map<String, String> yangResourcesContentByResourceName = mapYangResourcesToContent(resourceNames); + cpsModuleService.createSchemaSet(dataspaceName, schemaSetName, yangResourcesContentByResourceName); } catch (final AlreadyDefinedException alreadyDefinedException) { log.warn("Creating new schema set failed as schema set already exists"); } catch (final Exception exception) { log.error("Creating schema set failed: {} ", exception.getMessage()); - throw new NcmpStartUpException("Creating schema set failed", exception.getMessage()); + throw new ModelOnboardingException("Creating schema set failed", exception.getMessage()); } } - void createDataspace(final String dataspaceName) { + /** + * Create initial dataspace. + * @param dataspaceName dataspace name + */ + public void createDataspace(final String dataspaceName) { try { cpsDataspaceService.createDataspace(dataspaceName); } catch (final AlreadyDefinedException alreadyDefinedException) { log.debug("Dataspace already exists"); } catch (final Exception exception) { log.error("Creating dataspace failed: {} ", exception.getMessage()); - throw new NcmpStartUpException("Creating dataspace failed", exception.getMessage()); + throw new ModelOnboardingException("Creating dataspace failed", exception.getMessage()); } } - void deleteUnusedSchemaSets(final String dataspaceName, final String... schemaSetNames) { - for (final String schemaSetName : schemaSetNames) { - try { - cpsModuleService.deleteSchemaSet( - dataspaceName, schemaSetName, CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED); - } catch (final Exception exception) { - log.warn("Deleting schema set failed: {} ", exception.getMessage()); - } - } - } - - void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) { + /** + * Create initial anchor. + * @param dataspaceName dataspace name + * @param schemaSetName schemaset name + * @param anchorName anchor name + */ + public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) { try { cpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorName); } catch (final AlreadyDefinedException alreadyDefinedException) { log.warn("Creating new anchor failed as anchor already exists"); } catch (final Exception exception) { log.error("Creating anchor failed: {} ", exception.getMessage()); - throw new NcmpStartUpException("Creating anchor failed", exception.getMessage()); + throw new ModelOnboardingException("Creating anchor failed", exception.getMessage()); } } - void createTopLevelDataNode(final String dataspaceName, final String anchorName, final String dataNodeName) { + /** + * Create initial top level data node. + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param dataNodeName data node name + */ + public void createTopLevelDataNode(final String dataspaceName, final String anchorName, final String dataNodeName) { final String nodeData = jsonObjectMapper.asJsonString(Map.of(dataNodeName, Map.of())); try { cpsDataService.saveData(dataspaceName, anchorName, nodeData, OffsetDateTime.now()); @@ -123,35 +128,56 @@ abstract class AbstractModelLoader implements ModelLoader { log.warn("Creating new data node '{}' failed as data node already exists", dataNodeName); } catch (final Exception exception) { log.error("Creating data node failed: {}", exception.getMessage()); - throw new NcmpStartUpException("Creating data node failed", exception.getMessage()); + throw new ModelOnboardingException("Creating data node failed", exception.getMessage()); } } - void updateAnchorSchemaSet(final String dataspaceName, final String anchorName, final String schemaSetName) { + /** + * Delete unused schema set. + * @param dataspaceName dataspace name + * @param schemaSetNames schema set names + */ + public void deleteUnusedSchemaSets(final String dataspaceName, final String... schemaSetNames) { + for (final String schemaSetName : schemaSetNames) { + try { + cpsModuleService.deleteSchemaSet( + dataspaceName, schemaSetName, CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED); + } catch (final Exception exception) { + log.warn("Deleting schema set failed: {} ", exception.getMessage()); + } + } + } + + /** + * Update anchor schema set. + * @param dataspaceName dataspace name + * @param anchorName anchor name + * @param schemaSetName schemaset name + */ + public void updateAnchorSchemaSet(final String dataspaceName, final String anchorName, final String schemaSetName) { try { cpsAnchorService.updateAnchorSchemaSet(dataspaceName, anchorName, schemaSetName); } catch (final Exception exception) { log.error("Updating schema set failed: {}", exception.getMessage()); - throw new NcmpStartUpException("Updating schema set failed", exception.getMessage()); + throw new ModelOnboardingException("Updating schema set failed", exception.getMessage()); } } - Map<String, String> createYangResourcesToContentMap(final String... resourceNames) { - final Map<String, String> yangResourcesToContentMap = new HashMap<>(); + Map<String, String> mapYangResourcesToContent(final String... resourceNames) { + final Map<String, String> yangResourceContentByName = new HashMap<>(); for (final String resourceName: resourceNames) { - yangResourcesToContentMap.put(resourceName, getFileContentAsString("models/" + resourceName)); + yangResourceContentByName.put(resourceName, getFileContentAsString("models/" + resourceName)); } - return yangResourcesToContentMap; + return yangResourceContentByName; } private String getFileContentAsString(final String fileName) { - try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName)) { + try (final InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName)) { return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); } catch (final Exception exception) { final String message = String.format("Onboarding failed as unable to read file: %s", fileName); log.debug(message); - throw new NcmpStartUpException(message, exception.getMessage()); + throw new ModelOnboardingException(message, exception.getMessage()); } } - } diff --git a/cps-service/src/main/java/org/onap/cps/init/CpsNotificationSubscriptionModelLoader.java b/cps-service/src/main/java/org/onap/cps/init/CpsNotificationSubscriptionModelLoader.java new file mode 100644 index 0000000000..0b7d1609ff --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/init/CpsNotificationSubscriptionModelLoader.java @@ -0,0 +1,60 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.init; + +import lombok.extern.slf4j.Slf4j; +import org.onap.cps.api.CpsAnchorService; +import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsDataspaceService; +import org.onap.cps.api.CpsModuleService; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class CpsNotificationSubscriptionModelLoader extends AbstractModelLoader { + + private static final String MODEL_FILENAME = "cps-notification-subscriptions@2024-07-03.yang"; + private static final String SCHEMASET_NAME = "cps-notification-subscriptions"; + private static final String ANCHOR_NAME = "cps-notification-subscriptions"; + private static final String CPS_DATASPACE_NAME = "CPS-Admin"; + private static final String REGISTRY_DATANODE_NAME = "dataspaces"; + + public CpsNotificationSubscriptionModelLoader(final CpsDataspaceService cpsDataspaceService, + final CpsModuleService cpsModuleService, + final CpsAnchorService cpsAnchorService, + final CpsDataService cpsDataService) { + super(cpsDataspaceService, cpsModuleService, cpsAnchorService, cpsDataService); + } + + @Override + public void onboardOrUpgradeModel() { + onboardSubscriptionModels(); + log.info("Subscription models onboarded successfully"); + } + + private void onboardSubscriptionModels() { + createDataspace(CPS_DATASPACE_NAME); + createSchemaSet(CPS_DATASPACE_NAME, SCHEMASET_NAME, MODEL_FILENAME); + createAnchor(CPS_DATASPACE_NAME, SCHEMASET_NAME, ANCHOR_NAME); + createTopLevelDataNode(CPS_DATASPACE_NAME, ANCHOR_NAME, REGISTRY_DATANODE_NAME); + } + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java b/cps-service/src/main/java/org/onap/cps/init/ModelLoader.java index 9832ba3f9e..89c18b3159 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/ModelLoader.java +++ b/cps-service/src/main/java/org/onap/cps/init/ModelLoader.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023-2024 Nordix Foundation + * Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,15 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.init; +package org.onap.cps.init; -import lombok.NonNull; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; public interface ModelLoader extends ApplicationListener<ApplicationStartedEvent> { @Override - void onApplicationEvent(@NonNull ApplicationStartedEvent applicationStartedEvent); + void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent); void onboardOrUpgradeModel(); - } diff --git a/cps-service/src/main/resources/models/cps-notification-subscriptions@2024-07-03.yang b/cps-service/src/main/resources/models/cps-notification-subscriptions@2024-07-03.yang new file mode 100644 index 0000000000..1cab7923ea --- /dev/null +++ b/cps-service/src/main/resources/models/cps-notification-subscriptions@2024-07-03.yang @@ -0,0 +1,48 @@ +module cps-notification-subscriptions { + yang-version 1.1; + namespace "org:onap:cps"; + + prefix cps-notification-subscriptions; + + revision "2024-08-05" { + description + "First release of cps notification subscriptions model"; + } + container dataspaces { + + list dataspace { + key "name"; + + leaf name { + type string; + } + + container anchors { + + list anchor { + key "name"; + + leaf name { + type string; + } + + container xpaths { + + list xpath { + key "path"; + leaf path { + type string; + } + } + } + } + } + leaf-list subscriptionIds { + type string; + } + leaf topic { + type string; + } + } + } +}
\ No newline at end of file diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/init/AbstractModelLoaderSpec.groovy index 63192e40c6..0618cad951 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/init/AbstractModelLoaderSpec.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2023-2024 Nordix Foundation + * Modification Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.ncmp.init +package org.onap.cps.init import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger @@ -27,7 +28,7 @@ import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.exceptions.NcmpStartUpException +import org.onap.cps.api.exceptions.ModelOnboardingException import org.onap.cps.api.parameters.CascadeDeleteAllowed import org.onap.cps.api.exceptions.AlreadyDefinedException import org.slf4j.LoggerFactory @@ -46,12 +47,10 @@ class AbstractModelLoaderSpec extends Specification { def applicationContext = new AnnotationConfigApplicationContext() - def yangResourceToContentMap def logger = (Logger) LoggerFactory.getLogger(AbstractModelLoader) def loggingListAppender void setup() { - yangResourceToContentMap = objectUnderTest.createYangResourcesToContentMap('cm-data-subscriptions@2024-02-12.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) @@ -60,20 +59,21 @@ class AbstractModelLoaderSpec extends Specification { } void cleanup() { - ((Logger) LoggerFactory.getLogger(CmDataSubscriptionModelLoader.class)).detachAndStopAllAppenders() + logger.detachAndStopAllAppenders() applicationContext.close() + loggingListAppender.stop() } - def 'Application started event'() { + def 'Application started event triggers onboarding/upgrade'() { when: 'Application (started) event is triggered' objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) then: 'the onboard/upgrade method is executed' 1 * objectUnderTest.onboardOrUpgradeModel() } - def 'Application started event with start up exception'() { - given: 'a start up exception is thrown doing model onboarding' - objectUnderTest.onboardOrUpgradeModel() >> { throw new NcmpStartUpException('test message','details are not logged') } + def 'Application started event handles startup exception'() { + given: 'a startup exception is thrown during model onboarding' + objectUnderTest.onboardOrUpgradeModel() >> { throw new ModelOnboardingException('test message','details are not logged') } when: 'Application (started) event is triggered' objectUnderTest.onApplicationEvent(new ApplicationStartedEvent(new SpringApplication(), null, applicationContext, null)) then: 'the exception message is logged' @@ -81,116 +81,138 @@ class AbstractModelLoaderSpec extends Specification { assert logs.contains('test message') } - def 'Create schema set.'() { + def 'Creating a dataspace delegates to the service.'() { + when: 'creating a dataspace' + objectUnderTest.createDataspace('some dataspace') + then: 'the operation is delegated to the dataspace service' + 1 * mockCpsDataspaceService.createDataspace('some dataspace') + } + + def 'Creating a dataspace handles already defined exception.'() { + given: 'dataspace service throws an already defined exception' + mockCpsDataspaceService.createDataspace(*_) >> { throw AlreadyDefinedException.forDataNodes([], 'some context') } + when: 'creating a dataspace' + objectUnderTest.createDataspace('some dataspace') + then: 'the exception is ignored i.e. no exception thrown up' + noExceptionThrown() + and: 'the exception is ignored, and a log message is produced' + assertLogContains('Dataspace already exists') + } + + def 'Creating a dataspace handles other exception.'() { + given: 'dataspace service throws a runtime exception' + mockCpsDataspaceService.createDataspace(*_) >> { throw new RuntimeException('test message') } + when: 'creating a dataspace' + objectUnderTest.createDataspace('some dataspace') + then: 'a startup exception with correct message and details is thrown' + def thrown = thrown(ModelOnboardingException) + assert thrown.message.contains('Creating dataspace failed') + assert thrown.details.contains('test message') + } + + def 'Creating a schema set delegates to the service.'() { when: 'creating a schema set' - objectUnderTest.createSchemaSet('some dataspace','new name','cm-data-subscriptions@2024-02-12.yang') - then: 'the operation is delegated to the admin service' + objectUnderTest.createSchemaSet('some dataspace','new name','cps-notification-subscriptions@2024-07-03.yang') + then: 'the operation is delegated to the module service' 1 * mockCpsModuleService.createSchemaSet('some dataspace','new name',_) } - def 'Create schema set with already defined exception.'() { + def 'Creating a schema set handles already defined exception.'() { given: 'the module service throws an already defined exception' mockCpsModuleService.createSchemaSet(*_) >> { throw AlreadyDefinedException.forSchemaSet('name','context',null) } when: 'attempt to create a schema set' - objectUnderTest.createSchemaSet('some dataspace','new name','cm-data-subscriptions@2024-02-12.yang') - then: 'the exception is ignored i.e. no exception thrown up' + objectUnderTest.createSchemaSet('some dataspace','new name','cps-notification-subscriptions@2024-07-03.yang') + then: 'the exception is ignored, and a log message is produced' noExceptionThrown() - and: 'the exception message is logged' - def logs = loggingListAppender.list.toString() - assert logs.contains('Creating new schema set failed as schema set already exists') + assertLogContains('Creating new schema set failed as schema set already exists') } - def 'Create schema set with non existing yang file.'() { - when: 'attempt to create a schema set from a non existing file' + def 'Creating a schema set from a non-existing YANG file.'() { + when: 'attempting to create a schema set from a non-existing file' objectUnderTest.createSchemaSet('some dataspace','some name','no such yang file') then: 'a startup exception with correct message and details is thrown' - def thrown = thrown(NcmpStartUpException) + def thrown = thrown(ModelOnboardingException) assert thrown.message.contains('Creating schema set failed') assert thrown.details.contains('unable to read file') } - def 'Delete unused schema sets.'() { - when: 'several unused schemas are deleted ' - objectUnderTest.deleteUnusedSchemaSets('some dataspace','schema set 1', 'schema set 2') - then: 'a request to delete each (without cascade) is delegated to the module service' - 1 * mockCpsModuleService.deleteSchemaSet('some dataspace', 'schema set 1', CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED) - 1 * mockCpsModuleService.deleteSchemaSet('some dataspace', 'schema set 2', CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED) - - } - - def 'Delete unused schema sets with exception.'() { - given: 'deleting the first schemaset causes an exception' - mockCpsModuleService.deleteSchemaSet(_, 'schema set 1', _) >> { throw new RuntimeException('test message')} - when: 'several unused schemas are deleted ' - objectUnderTest.deleteUnusedSchemaSets('some dataspace','schema set 1', 'schema set 2') - then: 'the exception message is logged' - def logs = loggingListAppender.list.toString() - assert logs.contains('Deleting schema set failed') - assert logs.contains('test message') - and: 'the second schema set is still deleted' - 1 * mockCpsModuleService.deleteSchemaSet('some dataspace', 'schema set 2', CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED) - } - - def 'Create anchor.'() { + def 'Creating an anchor delegates to the service.'() { when: 'creating an anchor' objectUnderTest.createAnchor('some dataspace','some schema set','new name') - then: 'the operation is delegated to the admin service' + then: 'the operation is delegated to the anchor service' 1 * mockCpsAnchorService.createAnchor('some dataspace','some schema set', 'new name') } - def 'Create anchor with already defined exception.'() { - given: 'the admin service throws an already defined exception' + def 'Creating an anchor handles already defined exception.'() { + given: 'the anchor service throws an already defined exception' mockCpsAnchorService.createAnchor(*_)>> { throw AlreadyDefinedException.forAnchor('name','context',null) } - when: 'attempt to create anchor' + when: 'attempting to create an anchor' objectUnderTest.createAnchor('some dataspace','some schema set','new name') - then: 'the exception is ignored i.e. no exception thrown up' + then: 'the exception is ignored, and a log message is produced' noExceptionThrown() - and: 'the exception message is logged' - def logs = loggingListAppender.list.toString() - assert logs.contains('Creating new anchor failed as anchor already exists') + assertLogContains('Creating new anchor failed as anchor already exists') } - def 'Create anchor with any other exception.'() { - given: 'the admin service throws a exception' + def 'Creating an anchor handles other exceptions.'() { + given: 'the anchor service throws a runtime exception' mockCpsAnchorService.createAnchor(*_)>> { throw new RuntimeException('test message') } when: 'attempt to create anchor' objectUnderTest.createAnchor('some dataspace','some schema set','new name') then: 'a startup exception with correct message and details is thrown' - def thrown = thrown(NcmpStartUpException) + def thrown = thrown(ModelOnboardingException) assert thrown.message.contains('Creating anchor failed') assert thrown.details.contains('test message') } - def 'Create top level node.'() { - when: 'top level node is created' + def 'Creating a top-level data node delegates to the service.'() { + when: 'top-level node is created' objectUnderTest.createTopLevelDataNode('dataspace','anchor','new node') - then: 'the correct json is saved using the data service' + then: 'the correct JSON is saved using the data service' 1 * mockCpsDataService.saveData('dataspace','anchor', '{"new node":{}}',_) } - def 'Create top level node with already defined exception.'() { + def 'Creating a top-level node handles already defined exception.'() { given: 'the data service throws an Already Defined exception' mockCpsDataService.saveData(*_) >> { throw AlreadyDefinedException.forDataNodes([], 'some context') } - when: 'attempt to create top level node' + when: 'attempting to create a top-level node' objectUnderTest.createTopLevelDataNode('dataspace','anchor','new node') - then: 'the exception is ignored i.e. no exception thrown up' + then: 'the exception is ignored, and a log message is produced' noExceptionThrown() - and: 'the exception message is logged' - def logs = loggingListAppender.list.toString() - assert logs.contains('failed as data node already exists') + assertLogContains('failed as data node already exists') } - def 'Create top level node with any other exception.'() { - given: 'the data service throws an exception' + def 'Create a top-level node with any other exception.'() { + given: 'the data service throws a runtime exception' mockCpsDataService.saveData(*_) >> { throw new RuntimeException('test message') } - when: 'attempt to create top level node' + when: 'attempt to create a top-level node' objectUnderTest.createTopLevelDataNode('dataspace','anchor','new node') then: 'a startup exception with correct message and details is thrown' - def thrown = thrown(NcmpStartUpException) + def thrown = thrown(ModelOnboardingException) assert thrown.message.contains('Creating data node failed') assert thrown.details.contains('test message') } + def 'Delete unused schema sets delegates to the service.'() { + when: 'unused schema sets get deleted' + objectUnderTest.deleteUnusedSchemaSets('some dataspace','schema set 1', 'schema set 2') + then: 'a request to delete each (without cascade) is delegated to the module service' + 1 * mockCpsModuleService.deleteSchemaSet('some dataspace', 'schema set 1', CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED) + 1 * mockCpsModuleService.deleteSchemaSet('some dataspace', 'schema set 2', CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED) + } + + def 'Delete unused schema sets with exception.'() { + given: 'deleting the first schemaset causes an exception' + mockCpsModuleService.deleteSchemaSet(_, 'schema set 1', _) >> { throw new RuntimeException('test message')} + when: 'several unused schemas are deleted ' + objectUnderTest.deleteUnusedSchemaSets('some dataspace','schema set 1', 'schema set 2') + then: 'the exception message is logged' + def logs = loggingListAppender.list.toString() + assert logs.contains('Deleting schema set failed') + assert logs.contains('test message') + and: 'the second schema set is still deleted' + 1 * mockCpsModuleService.deleteSchemaSet('some dataspace', 'schema set 2', CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED) + } + def 'Update anchor schema set.'() { when: 'a schema set for an anchor is updated' objectUnderTest.updateAnchorSchemaSet('some dataspace', 'anchor', 'new schema set') @@ -204,11 +226,16 @@ class AbstractModelLoaderSpec extends Specification { when: 'a schema set for an anchor is updated' objectUnderTest.updateAnchorSchemaSet('some dataspace', 'anchor', 'new schema set') then: 'a startup exception with correct message and details is thrown' - def thrown = thrown(NcmpStartUpException) + def thrown = thrown(ModelOnboardingException) assert thrown.message.contains('Updating schema set failed') assert thrown.details.contains('test message') } + private void assertLogContains(String message) { + def logs = loggingListAppender.list.toString() + assert logs.contains(message) + } + class TestModelLoader extends AbstractModelLoader { TestModelLoader(final CpsDataspaceService cpsDataspaceService, @@ -216,12 +243,11 @@ class AbstractModelLoaderSpec extends Specification { final CpsAnchorService cpsAnchorService, final CpsDataService cpsDataService) { super(cpsDataspaceService, cpsModuleService, cpsAnchorService, cpsDataService) - super.maximumAttemptCount = 2 - super.retryTimeMs = 1 } @Override - void onboardOrUpgradeModel() { } + void onboardOrUpgradeModel() { + // No operation needed for testing + } } - } diff --git a/cps-service/src/test/groovy/org/onap/cps/init/CpsNotificationSubscriptionModelLoaderSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/init/CpsNotificationSubscriptionModelLoaderSpec.groovy new file mode 100644 index 0000000000..0d515f90ac --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/init/CpsNotificationSubscriptionModelLoaderSpec.groovy @@ -0,0 +1,87 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 TechMahindra Ltd. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.init + +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.core.read.ListAppender +import org.onap.cps.api.CpsAnchorService +import org.onap.cps.api.CpsDataService +import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsModuleService +import org.onap.cps.api.model.Dataspace +import org.slf4j.LoggerFactory +import org.springframework.boot.context.event.ApplicationStartedEvent +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import spock.lang.Specification + +class CpsNotificationSubscriptionModelLoaderSpec extends Specification { + def mockCpsDataspaceService = Mock(CpsDataspaceService) + def mockCpsModuleService = Mock(CpsModuleService) + def mockCpsDataService = Mock(CpsDataService) + def mockCpsAnchorService = Mock(CpsAnchorService) + def objectUnderTest = new CpsNotificationSubscriptionModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsAnchorService, mockCpsDataService) + + def applicationContext = new AnnotationConfigApplicationContext() + + def expectedYangResourcesToContents + def logger = (Logger) LoggerFactory.getLogger(objectUnderTest.class) + def loggingListAppender + + def CPS_DATASPACE_NAME = 'CPS-Admin' + def ANCHOR_NAME = 'cps-notification-subscriptions' + def SCHEMASET_NAME = 'cps-notification-subscriptions' + def MODEL_FILENAME = 'cps-notification-subscriptions@2024-07-03.yang' + + void setup() { + expectedYangResourcesToContents = objectUnderTest.mapYangResourcesToContent(MODEL_FILENAME) + logger.setLevel(Level.DEBUG) + loggingListAppender = new ListAppender() + logger.addAppender(loggingListAppender) + loggingListAppender.start() + applicationContext.refresh() + } + + void cleanup() { + logger.detachAndStopAllAppenders() + applicationContext.close() + loggingListAppender.stop() + } + + def 'Onboard subscription model via application started event.'() { + given: 'dataspace is already present' + mockCpsDataspaceService.getAllDataspaces() >> [new Dataspace('test')] + when: 'the application is ready' + objectUnderTest.onApplicationEvent(Mock(ApplicationStartedEvent)) + then: 'the module service to create schema set is called once' + 1 * mockCpsModuleService.createSchemaSet(CPS_DATASPACE_NAME, SCHEMASET_NAME, expectedYangResourcesToContents) + and: 'the anchor service to create an anchor set is called once' + 1 * mockCpsAnchorService.createAnchor(CPS_DATASPACE_NAME, SCHEMASET_NAME, ANCHOR_NAME) + and: 'the data service to create a top level datanode is called once' + 1 * mockCpsDataService.saveData(CPS_DATASPACE_NAME, ANCHOR_NAME, '{"dataspaces":{}}', _) + } + + private void assertLogContains(String message) { + def logs = loggingListAppender.list.toString() + assert logs.contains(message) + } + +} |