diff options
67 files changed, 802 insertions, 1573 deletions
diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index 89969b9c6a..8dafdec11a 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -114,10 +114,6 @@ app: notification: enabled: true - data-updated: - topic: ${CPS_CHANGE_EVENT_TOPIC:cps.data-updated-events} - filters: - enabled-dataspaces: ${NOTIFICATION_DATASPACE_FILTER_PATTERNS:""} async: executor: core-pool-size: 2 diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy index 870c36c777..55590168d9 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/executor/CpsNcmpTaskExecutorSpec.groovy @@ -26,7 +26,6 @@ import ch.qos.logback.classic.spi.ILoggingEvent import ch.qos.logback.core.read.ListAppender import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach -import org.onap.cps.notification.NotificationErrorHandler import org.slf4j.LoggerFactory import spock.lang.Specification @@ -44,7 +43,7 @@ class CpsNcmpTaskExecutorSpec extends Specification { @AfterEach void teardown() { - ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).detachAndStopAllAppenders(); + ((Logger) LoggerFactory.getLogger(CpsNcmpTaskExecutor.class)).detachAndStopAllAppenders(); } def 'Execute successful task.'() { diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index f448c8f47c..e9faf43498 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -42,6 +42,10 @@ <artifactId>commons-lang3</artifactId> </dependency> <dependency> + <groupId>org.apache.httpcomponents.client5</groupId> + <artifactId>httpclient5</artifactId> + </dependency> + <dependency> <groupId>io.cloudevents</groupId> <artifactId>cloudevents-json-jackson</artifactId> </dependency> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java new file mode 100644 index 0000000000..aaa4f1e5bb --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/HttpClientConfiguration.java @@ -0,0 +1,57 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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.ncmp.api.impl.config; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.convert.DurationUnit; + +@Getter +@Setter +@ConfigurationProperties(prefix = "httpclient5", ignoreUnknownFields = true) +public class HttpClientConfiguration { + + /** + * The maximum time to establish a connection. + */ + @DurationUnit(ChronoUnit.SECONDS) + private Duration connectionTimeoutInSeconds = Duration.ofSeconds(180); + + /** + * The maximum number of open connections per route. + */ + private int maximumConnectionsPerRoute = 50; + + /** + * The maximum total number of open connections. + */ + private int maximumConnectionsTotal = maximumConnectionsPerRoute * 2; + + /** + * The duration after which idle connections are evicted. + */ + @DurationUnit(ChronoUnit.SECONDS) + private Duration idleConnectionEvictionThresholdInSeconds = Duration.ofSeconds(5); + +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java index ffecf9c7f1..4460094f54 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/config/NcmpConfiguration.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021-2022 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,29 +20,36 @@ package org.onap.cps.ncmp.api.impl.config; -import java.time.Duration; import java.util.Arrays; import lombok.AccessLevel; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Configuration +@EnableConfigurationProperties(HttpClientConfiguration.class) @RequiredArgsConstructor(access = AccessLevel.PROTECTED) public class NcmpConfiguration { - private static final Duration CONNECTION_TIMEOUT_MILLISECONDS = Duration.ofMillis(180000); - private static final Duration READ_TIMEOUT_MILLISECONDS = Duration.ofMillis(180000); - @Getter @Component public static class DmiProperties { @@ -60,13 +67,38 @@ public class NcmpConfiguration { * Rest template bean. * * @param restTemplateBuilder the rest template builder + * @param httpClientConfiguration the http client configuration * @return rest template instance */ @Bean @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) - public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) { - final RestTemplate restTemplate = restTemplateBuilder.setConnectTimeout(CONNECTION_TIMEOUT_MILLISECONDS) - .setReadTimeout(READ_TIMEOUT_MILLISECONDS).build(); + public static RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder, + final HttpClientConfiguration httpClientConfiguration) { + + final ConnectionConfig connectionConfig = ConnectionConfig.copy(ConnectionConfig.DEFAULT) + .setConnectTimeout(Timeout.of(httpClientConfiguration.getConnectionTimeoutInSeconds())) + .build(); + + final PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() + .setDefaultConnectionConfig(connectionConfig) + .setMaxConnTotal(httpClientConfiguration.getMaximumConnectionsTotal()) + .setMaxConnPerRoute(httpClientConfiguration.getMaximumConnectionsPerRoute()) + .build(); + + final CloseableHttpClient httpClient = HttpClients.custom() + .setConnectionManager(connectionManager) + .evictExpiredConnections() + .evictIdleConnections( + TimeValue.of(httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds())) + .build(); + + final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); + + final RestTemplate restTemplate = restTemplateBuilder + .requestFactory(() -> requestFactory) + .setConnectTimeout(httpClientConfiguration.getConnectionTimeoutInSeconds()) + .build(); + setRestTemplateMessageConverters(restTemplate); return restTemplate; } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java index 159d8f345a..a0aeac3e89 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImpl.java @@ -29,7 +29,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.impl.utils.YangDataConverter; @@ -48,7 +48,7 @@ import org.springframework.stereotype.Component; public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements InventoryPersistence { private final CpsModuleService cpsModuleService; - private final CpsAdminService cpsAdminService; + private final CpsAnchorService cpsAnchorService; private final CpsValidator cpsValidator; /** @@ -58,14 +58,14 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv * @param cpsDataService cps data service instance * @param cpsModuleService cps module service instance * @param cpsValidator cps validation service instance - * @param cpsAdminService cps admin service instance + * @param cpsAnchorService cps anchor service instance */ public InventoryPersistenceImpl(final JsonObjectMapper jsonObjectMapper, final CpsDataService cpsDataService, final CpsModuleService cpsModuleService, final CpsValidator cpsValidator, - final CpsAdminService cpsAdminService) { + final CpsAnchorService cpsAnchorService) { super(jsonObjectMapper, cpsDataService, cpsModuleService, cpsValidator); this.cpsModuleService = cpsModuleService; - this.cpsAdminService = cpsAdminService; + this.cpsAnchorService = cpsAnchorService; this.cpsValidator = cpsValidator; } @@ -160,7 +160,7 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv @Override public Collection<String> getCmHandleIdsWithGivenModules(final Collection<String> moduleNamesForQuery) { - return cpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); } private static String createCmHandleXPath(final String cmHandleId) { diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java index a6b85a5cab..b21a2f1f85 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncService.java @@ -35,7 +35,7 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.ncmp.api.impl.inventory.CmHandleQueries; @@ -59,9 +59,9 @@ public class ModuleSyncService { private final DmiModelOperations dmiModelOperations; private final CpsModuleService cpsModuleService; - private final CpsAdminService cpsAdminService; private final CmHandleQueries cmHandleQueries; private final CpsDataService cpsDataService; + private final CpsAnchorService cpsAnchorService; private final JsonObjectMapper jsonObjectMapper; private final Map<String, Collection<ModuleReference>> moduleSetTagCache; private static final Map<String, String> NO_NEW_MODULES = Collections.emptyMap(); @@ -105,7 +105,7 @@ public class ModuleSyncService { } } if (!inUpgrade) { - cpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cmHandleId); + cpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, cmHandleId, cmHandleId); } setCmHandleModuleSetTag(yangModelCmHandle, moduleSetTag); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java index fd5f2b0ed6..bd8dec4dc8 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/init/AbstractModelLoader.java @@ -29,8 +29,9 @@ import java.util.Map; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; +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.api.impl.exception.NcmpStartUpException; import org.onap.cps.spi.CascadeDeleteAllowed; @@ -44,9 +45,10 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; @RequiredArgsConstructor abstract class AbstractModelLoader implements ModelLoader { - private final CpsAdminService cpsAdminService; + private final CpsDataspaceService cpsDataspaceService; private final CpsModuleService cpsModuleService; private final CpsDataService cpsDataService; + private final CpsAnchorService cpsAnchorService; private static final int EXIT_CODE_ON_ERROR = 1; @@ -71,7 +73,7 @@ abstract class AbstractModelLoader implements ModelLoader { void waitUntilDataspaceIsAvailable(final String dataspaceName) { log.info("Model Loader start-up, waiting for database to be ready"); int attemptCount = 0; - while (cpsAdminService.getDataspace(dataspaceName) == null) { + while (cpsDataspaceService.getDataspace(dataspaceName) == null) { if (attemptCount < maximumAttemptCount) { try { Thread.sleep(attemptCount * retryTimeMs); @@ -111,7 +113,7 @@ abstract class AbstractModelLoader implements ModelLoader { void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) { try { - cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName); + cpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorName); } catch (final AlreadyDefinedException alreadyDefinedException) { log.warn("Creating new anchor failed as anchor already exists"); } catch (final Exception exception) { @@ -134,7 +136,7 @@ abstract class AbstractModelLoader implements ModelLoader { void updateAnchorSchemaSet(final String dataspaceName, final String anchorName, final String schemaSetName) { try { - cpsAdminService.updateAnchorSchemaSet(dataspaceName, anchorName, schemaSetName); + 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()); 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 ade31e9ce6..c0f0279ad4 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 @@ -23,8 +23,9 @@ package org.onap.cps.ncmp.init; import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; +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.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -45,10 +46,11 @@ public class CmDataSubscriptionModelLoader extends AbstractModelLoader { - public CmDataSubscriptionModelLoader(final CpsAdminService cpsAdminService, + public CmDataSubscriptionModelLoader(final CpsDataspaceService cpsDataspaceService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService) { - super(cpsAdminService, cpsModuleService, cpsDataService); + final CpsDataService cpsDataService, + final CpsAnchorService cpsAnchorService) { + super(cpsDataspaceService, cpsModuleService, cpsDataService, cpsAnchorService); } @Value("${ncmp.model-loader.subscription:true}") 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 b805cdcd84..0e562cdd8b 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 @@ -24,8 +24,9 @@ import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DA import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; +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; @@ -33,13 +34,14 @@ import org.springframework.stereotype.Service; @Service public class InventoryModelLoader extends AbstractModelLoader { - private static final String NEW_MODEL_FILE_NAME = "dmi-registry@2023-08-23.yang"; - private static final String NEW_SCHEMA_SET_NAME = "dmi-registry-2023-08-23"; + private static final String NEW_MODEL_FILE_NAME = "dmi-registry@2023-11-27.yang"; + private static final String NEW_SCHEMA_SET_NAME = "dmi-registry-2023-11-27"; - public InventoryModelLoader(final CpsAdminService cpsAdminService, + public InventoryModelLoader(final CpsDataspaceService cpsDataspaceService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService) { - super(cpsAdminService, cpsModuleService, cpsDataService); + final CpsDataService cpsDataService, + final CpsAnchorService cpsAnchorService) { + super(cpsDataspaceService, cpsModuleService, cpsDataService, cpsAnchorService); } @Override diff --git a/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2022-05-10.yang b/cps-ncmp-service/src/main/resources/models/dmi-registry@2023-11-27.yang index 77517968c6..808bbdd1bc 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2022-05-10.yang +++ b/cps-ncmp-service/src/main/resources/models/dmi-registry@2023-11-27.yang @@ -8,19 +8,29 @@ module dmi-registry { contact "toine.siebelink@est.tech"; + revision "2023-11-27" { + description + "Added alternate-id"; + } + + revision "2023-08-23" { + description + "Added module-set-tag"; + } + revision "2022-05-10" { description - "Added DataSyncEnabled, SyncState with State, LastSyncTime, DataStoreSyncState with Operational and Running syncstate"; + "Added data-sync-enabled, sync-state with state, last-sync-time, data-store-sync-state with operational and running syncstate"; } revision "2022-02-10" { description - "Added State, LockReason, LockReasonDetails to aid with cmHandle sync and timestamp to aid with retry/timeout scenarios"; + "Added state, lock-reason, lock-reason-details to aid with cmHandle sync and timestamp to aid with retry/timeout scenarios"; } revision "2021-12-13" { description - "Added new list of public additional properties for a Cm-Handle which are exposed to clients of the NCMP interface"; + "Added new list of public-properties and additional-properties for a Cm-Handle which are exposed to clients of the NCMP interface"; } revision "2021-10-20" { @@ -75,6 +85,12 @@ module dmi-registry { leaf dmi-model-service-name { type string; } + leaf module-set-tag { + type string; + } + leaf alternate-id { + type string; + } list additional-properties { key "name"; @@ -120,4 +136,5 @@ module dmi-registry { } } } -}
\ No newline at end of file +} + diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy index 51b00d1431..f565ede394 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/NetworkCmProxyDataServiceImplRegistrationSpec.groovy @@ -210,12 +210,10 @@ class NetworkCmProxyDataServiceImplRegistrationSpec extends Specification { } and: 'state handler is invoked with the expected parameters' 1 * mockLcmEventsCmHandleStateHandler.initiateStateAdvised(_) >> { - args -> - { - def cmHandleStatePerCmHandle = (args[0] as Map) - cmHandleStatePerCmHandle.each { - assert (it.id == 'cmhandle' && it.dmiServiceName == 'my-server') - } + args -> { + def yangModelCmHandles = args[0] + assert yangModelCmHandles.id == ['cmhandle'] + assert yangModelCmHandles.dmiServiceName == ['my-server'] } } where: diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy new file mode 100644 index 0000000000..941c8b8a70 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/HttpClientConfigurationSpec.groovy @@ -0,0 +1,48 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation. + * ================================================================================ + * 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.ncmp.api.impl.config + +import java.time.Duration +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.context.properties.EnableConfigurationProperties +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.test.context.ContextConfiguration +import org.springframework.test.context.TestPropertySource +import org.springframework.test.context.support.AnnotationConfigContextLoader +import spock.lang.Specification + +@SpringBootTest +@ContextConfiguration(classes = [HttpClientConfiguration]) +@EnableConfigurationProperties(HttpClientConfiguration.class) +@TestPropertySource(properties = ["httpclient5.connectionTimeoutInSeconds=1", "httpclient5.maximumConnectionsTotal=200"]) +class HttpClientConfigurationSpec extends Specification { + + @Autowired + private HttpClientConfiguration httpClientConfiguration + + def 'Test HttpClientConfiguration properties with custom and default values'() { + expect: 'custom property values' + httpClientConfiguration.getConnectionTimeoutInSeconds() == Duration.ofSeconds(1) + httpClientConfiguration.getMaximumConnectionsTotal() == 200 + and: 'default property values' + httpClientConfiguration.getMaximumConnectionsPerRoute() == 50 + httpClientConfiguration.getIdleConnectionEvictionThresholdInSeconds() == Duration.ofSeconds(5) + } +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy index e1aba79a50..a4df9b37cf 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/config/NcmpConfigurationSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2021 Nordix Foundation + * Copyright (C) 2021-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,22 +19,27 @@ */ package org.onap.cps.ncmp.api.impl.config +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.web.client.RestTemplateBuilder import org.springframework.http.MediaType +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter import org.springframework.test.context.ContextConfiguration import org.springframework.web.client.RestTemplate import spock.lang.Specification @SpringBootTest -@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties]) +@ContextConfiguration(classes = [NcmpConfiguration.DmiProperties, HttpClientConfiguration]) class NcmpConfigurationSpec extends Specification{ @Autowired NcmpConfiguration.DmiProperties dmiProperties - + + @Autowired + HttpClientConfiguration httpClientConfiguration + def mockRestTemplateBuilder = new RestTemplateBuilder() def 'NcmpConfiguration Construction.'() { @@ -48,11 +53,14 @@ class NcmpConfigurationSpec extends Specification{ dmiProperties.authPassword == 'some-password' } - def 'Rest Template creation.'() { + def 'Rest Template creation with CloseableHttpClient and MappingJackson2HttpMessageConverter.'() { when: 'a rest template is created' - def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder) + def result = NcmpConfiguration.restTemplate(mockRestTemplateBuilder, httpClientConfiguration) then: 'the rest template is returned' assert result instanceof RestTemplate + and: 'the rest template is created with httpclient5' + assert result.getRequestFactory() instanceof HttpComponentsClientHttpRequestFactory + assert ((HttpComponentsClientHttpRequestFactory) result.getRequestFactory()).getHttpClient() instanceof CloseableHttpClient; and: 'a jackson media converter has been added' def lastMessageConverter = result.getMessageConverters().get(result.getMessageConverters().size()-1) lastMessageConverter instanceof MappingJackson2HttpMessageConverter diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy index bb4eebd40e..297f18c989 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -22,6 +22,8 @@ package org.onap.cps.ncmp.api.impl.inventory +import org.onap.cps.api.CpsAnchorService + import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -30,12 +32,8 @@ import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NO_TIME import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService -import org.onap.cps.ncmp.api.impl.inventory.CmHandleState -import org.onap.cps.ncmp.api.impl.inventory.CompositeState -import org.onap.cps.ncmp.api.impl.inventory.InventoryPersistenceImpl import org.onap.cps.ncmp.api.impl.yangmodels.YangModelCmHandle import org.onap.cps.spi.CascadeDeleteAllowed import org.onap.cps.spi.FetchDescendantsOption @@ -58,12 +56,12 @@ class InventoryPersistenceImplSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsAnchorService = Mock(CpsAnchorService) def mockCpsValidator = Mock(CpsValidator) def objectUnderTest = new InventoryPersistenceImpl(spiedJsonObjectMapper, mockCpsDataService, mockCpsModuleService, - mockCpsValidator, mockCpsAdminService) + mockCpsValidator, mockCpsAnchorService) def formattedDateAndTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ") .format(OffsetDateTime.of(2022, 12, 31, 20, 30, 40, 1, ZoneOffset.UTC)) @@ -284,7 +282,7 @@ class InventoryPersistenceImplSpec extends Specification { when: 'the method to get cm handles is called' objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name']) then: 'the admin persistence service method to query anchors is invoked once with the same parameter' - 1 * mockCpsAdminService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) + 1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) } def 'Replace list content'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy index 2ded84fac1..de783ed2ca 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/api/impl/inventory/sync/ModuleSyncServiceSpec.groovy @@ -20,13 +20,14 @@ package org.onap.cps.ncmp.api.impl.inventory.sync +import org.onap.cps.api.CpsAnchorService + import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory.MODULE_UPGRADE import org.onap.cps.ncmp.api.impl.inventory.CmHandleState import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.model.DataNode -import org.onap.cps.api.CpsAdminService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.model.DataNodeBuilder @@ -45,14 +46,14 @@ class ModuleSyncServiceSpec extends Specification { def mockCpsModuleService = Mock(CpsModuleService) def mockDmiModelOperations = Mock(DmiModelOperations) - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsAnchorService = Mock(CpsAnchorService) def mockCmHandleQueries = Mock(CmHandleQueries) def mockCpsDataService = Mock(CpsDataService) def mockJsonObjectMapper = Mock(JsonObjectMapper) def mockModuleSetTagCache = [:] - def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, mockCpsAdminService, - mockCmHandleQueries, mockCpsDataService, mockJsonObjectMapper, mockModuleSetTagCache) + def objectUnderTest = new ModuleSyncService(mockDmiModelOperations, mockCpsModuleService, + mockCmHandleQueries, mockCpsDataService, mockCpsAnchorService, mockJsonObjectMapper, mockModuleSetTagCache) def expectedDataspaceName = NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME def static cmHandleWithModuleSetTag = new DataNodeBuilder().withXpath("//cm-handles[@module-set-tag='tag-1'][@id='otherId']").withAnchor('otherId').build() @@ -78,7 +79,7 @@ class ModuleSyncServiceSpec extends Specification { then: 'create schema set from module is invoked with correct parameters' 1 * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', newModuleNameContentToMap, moduleReferences) and: 'anchor is created with the correct parameters' - 1 * mockCpsAdminService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1') + 1 * mockCpsAnchorService.createAnchor(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'ch-1', 'ch-1') where: 'the following parameters are used' scenario | existingModuleResourcesInCps | identifiedNewModuleReferences | newModuleNameContentToMap | moduleSetTag 'one new module' | [['module2': '2'], ['module3': '3']] | [['module1': '1']] | [module1: 'some yang source'] | '' @@ -115,7 +116,7 @@ class ModuleSyncServiceSpec extends Specification { and: 'create schema set from module is invoked for the upgraded cm handle' expectedCallsToCeateSchemaSet * mockCpsModuleService.createSchemaSetFromModules(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, 'upgraded-ch', [:], moduleReferences) and: 'No anchor is created for the upgraded cm handle' - 0 * mockCpsAdminService.createAnchor(*_) + 0 * mockCpsAnchorService.createAnchor(*_) where: 'the following parameters are used' scenario | populateCache | existingCmHandlesWithSameTag || expectedCallsToUpgradeSchemaSet | expectedCallsToCeateSchemaSet 'new' | false | [] || 0 | 1 diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy index e5ed21f1c4..3b1c25ba6a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/init/AbstractModelLoaderSpec.groovy @@ -23,7 +23,8 @@ package org.onap.cps.ncmp.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.CpsAdminService +import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.ncmp.api.impl.exception.NcmpStartUpException @@ -37,10 +38,11 @@ import spock.lang.Specification class AbstractModelLoaderSpec extends Specification { - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsDataspaceService = Mock(CpsDataspaceService) def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) - def objectUnderTest = Spy(new TestModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService)) + def mockCpsAnchorService = Mock(CpsAnchorService) + def objectUnderTest = Spy(new TestModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService)) def applicationContext = new AnnotationConfigApplicationContext() @@ -141,12 +143,12 @@ class AbstractModelLoaderSpec extends Specification { when: 'creating an anchor' objectUnderTest.createAnchor('some dataspace','some schema set','new name') then: 'the operation is delegated to the admin service' - 1 * mockCpsAdminService.createAnchor('some dataspace','some schema set', 'new name') + 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' - mockCpsAdminService.createAnchor(*_)>> { throw AlreadyDefinedException.forAnchor('name','context',null) } + mockCpsAnchorService.createAnchor(*_)>> { throw AlreadyDefinedException.forAnchor('name','context',null) } when: 'attempt to create anchor' objectUnderTest.createAnchor('some dataspace','some schema set','new name') then: 'the exception is ignored i.e. no exception thrown up' @@ -158,7 +160,7 @@ class AbstractModelLoaderSpec extends Specification { def 'Create anchor with any other exception.'() { given: 'the admin service throws a exception' - mockCpsAdminService.createAnchor(*_)>> { throw new RuntimeException('test message') } + 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' @@ -201,12 +203,12 @@ class AbstractModelLoaderSpec extends Specification { when: 'a schema set for an anchor is updated' objectUnderTest.updateAnchorSchemaSet('some dataspace', 'anchor', 'new schema set') then: 'the request is delegated to the admin service' - 1 * mockCpsAdminService.updateAnchorSchemaSet('some dataspace', 'anchor', 'new schema set') + 1 * mockCpsAnchorService.updateAnchorSchemaSet('some dataspace', 'anchor', 'new schema set') } def 'Update anchor schema set with exception.'() { given: 'the admin service throws an exception' - mockCpsAdminService.updateAnchorSchemaSet(*_) >> { throw new RuntimeException('test message') } + mockCpsAnchorService.updateAnchorSchemaSet(*_) >> { throw new RuntimeException('test message') } 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' @@ -217,10 +219,11 @@ class AbstractModelLoaderSpec extends Specification { class TestModelLoader extends AbstractModelLoader { - TestModelLoader(final CpsAdminService cpsAdminService, + TestModelLoader(final CpsDataspaceService cpsDataspaceService, final CpsModuleService cpsModuleService, - final CpsDataService cpsDataService) { - super(cpsAdminService, cpsModuleService, cpsDataService) + final CpsDataService cpsDataService, + final CpsAnchorService cpsAnchorService) { + super(cpsDataspaceService, cpsModuleService, cpsDataService, cpsAnchorService) super.maximumAttemptCount = 2 super.retryTimeMs = 1 } 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 06627129a9..aed495ec45 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 @@ -20,12 +20,14 @@ package org.onap.cps.ncmp.init +import org.onap.cps.api.CpsAnchorService + import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.model.Dataspace @@ -36,10 +38,11 @@ import spock.lang.Specification class CmDataSubscriptionModelLoaderSpec extends Specification { - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsDataspaceService = Mock(CpsDataspaceService) def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) - def objectUnderTest = new CmDataSubscriptionModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService) + def mockCpsAnchorService = Mock(CpsAnchorService) + def objectUnderTest = new CmDataSubscriptionModelLoader(mockCpsDataspaceService, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService) def applicationContext = new AnnotationConfigApplicationContext() @@ -65,13 +68,13 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { given:'model loader is enabled' objectUnderTest.subscriptionModelLoaderEnabled = true and: 'dataspace is ready for use' - mockCpsAdminService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('') + mockCpsDataspaceService.getDataspace(NCMP_DATASPACE_NAME) >> new Dataspace('') when: 'the application is ready' objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) then: 'the module service to create schema set is called once' 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', expectedYangResourcesToContentMap) and: 'the admin service to create an anchor set is called once' - 1 * mockCpsAdminService.createAnchor(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', 'cm-data-subscriptions') + 1 * mockCpsAnchorService.createAnchor(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', 'cm-data-subscriptions') and: 'the data service to create a top level datanode is called once' 1 * mockCpsDataService.saveData(NCMP_DATASPACE_NAME, 'cm-data-subscriptions', '{"datastores":{}}', _) } @@ -82,7 +85,7 @@ class CmDataSubscriptionModelLoaderSpec extends Specification { when: 'application is ready' objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) then: 'no interaction with admin service' - 0 * mockCpsAdminService.getDataspace(_) + 0 * mockCpsDataspaceService.getDataspace(_) then: 'a message is logged that the function is disabled' def logs = loggingListAppender.list.toString() assert logs.contains('Subscription Model Loader is disabled') 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 43e0f69b3f..5557993abd 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 @@ -20,13 +20,15 @@ package org.onap.cps.ncmp.init +import org.onap.cps.api.CpsAnchorService + import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.api.impl.ncmppersistence.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR import ch.qos.logback.classic.Level import ch.qos.logback.classic.Logger import ch.qos.logback.core.read.ListAppender -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.model.Dataspace @@ -37,10 +39,11 @@ import spock.lang.Specification class InventoryModelLoaderSpec extends Specification { - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsAdminService = Mock(CpsDataspaceService) def mockCpsModuleService = Mock(CpsModuleService) def mockCpsDataService = Mock(CpsDataService) - def objectUnderTest = new InventoryModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService) + def mockCpsAnchorService = Mock(CpsAnchorService) + def objectUnderTest = new InventoryModelLoader(mockCpsAdminService, mockCpsModuleService, mockCpsDataService, mockCpsAnchorService) def applicationContext = new AnnotationConfigApplicationContext() @@ -49,7 +52,7 @@ class InventoryModelLoaderSpec extends Specification { def loggingListAppender void setup() { - expectedYangResourceToContentMap = objectUnderTest.createYangResourcesToContentMap('dmi-registry@2023-08-23.yang') + expectedYangResourceToContentMap = objectUnderTest.createYangResourcesToContentMap('dmi-registry@2023-11-27.yang') logger.setLevel(Level.DEBUG) loggingListAppender = new ListAppender() logger.addAppender(loggingListAppender) @@ -68,9 +71,9 @@ class InventoryModelLoaderSpec extends Specification { when: 'the application is ready' objectUnderTest.onApplicationEvent(Mock(ApplicationReadyEvent)) then: 'the module service is used to create the new schema set from the correct resource' - 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'dmi-registry-2023-08-23', expectedYangResourceToContentMap) + 1 * mockCpsModuleService.createSchemaSet(NCMP_DATASPACE_NAME, 'dmi-registry-2023-11-27', expectedYangResourceToContentMap) and: 'the admin service is used to update the anchor' - 1 * mockCpsAdminService.updateAnchorSchemaSet(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'dmi-registry-2023-08-23') + 1 * mockCpsAnchorService.updateAnchorSchemaSet(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, 'dmi-registry-2023-11-27') and: 'No schema sets are being removed by the module service (yet)' 0 * mockCpsModuleService.deleteSchemaSet(NCMP_DATASPACE_NAME, _, _) } diff --git a/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java b/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java index 993dad58c3..9b78f85201 100755 --- a/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java +++ b/cps-rest/src/main/java/org/onap/cps/rest/controller/AdminRestController.java @@ -33,7 +33,8 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsAnchorService; +import org.onap.cps.api.CpsDataspaceService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.rest.api.CpsAdminApi; import org.onap.cps.rest.model.AnchorDetails; @@ -53,9 +54,10 @@ import org.springframework.web.multipart.MultipartFile; @RequiredArgsConstructor public class AdminRestController implements CpsAdminApi { - private final CpsAdminService cpsAdminService; + private final CpsDataspaceService cpsDataspaceService; private final CpsModuleService cpsModuleService; private final CpsRestInputMapper cpsRestInputMapper; + private final CpsAnchorService cpsAnchorService; /** * Create a dataspace. @@ -65,7 +67,7 @@ public class AdminRestController implements CpsAdminApi { */ @Override public ResponseEntity<String> createDataspace(@NotNull @Valid final String dataspaceName) { - cpsAdminService.createDataspace(dataspaceName); + cpsDataspaceService.createDataspace(dataspaceName); return new ResponseEntity<>(dataspaceName, HttpStatus.CREATED); } @@ -77,7 +79,7 @@ public class AdminRestController implements CpsAdminApi { */ @Override public ResponseEntity<Void> createDataspaceV2(@NotNull @Valid final String dataspaceName) { - cpsAdminService.createDataspace(dataspaceName); + cpsDataspaceService.createDataspace(dataspaceName); return new ResponseEntity<>(HttpStatus.CREATED); } @@ -89,7 +91,7 @@ public class AdminRestController implements CpsAdminApi { */ @Override public ResponseEntity<Void> deleteDataspace(final String apiVersion, final String dataspaceName) { - cpsAdminService.deleteDataspace(dataspaceName); + cpsDataspaceService.deleteDataspace(dataspaceName); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -184,7 +186,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<String> createAnchor(final String dataspaceName, @NotNull @Valid final String schemaSetName, @NotNull @Valid final String anchorName) { - cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName); + cpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorName); return new ResponseEntity<>(anchorName, HttpStatus.CREATED); } @@ -199,7 +201,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<Void> createAnchorV2(final String dataspaceName, @NotNull @Valid final String schemaSetName, @NotNull @Valid final String anchorName) { - cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName); + cpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorName); return new ResponseEntity<>(HttpStatus.CREATED); } @@ -214,7 +216,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<Void> deleteAnchor(final String apiVersion, final String dataspaceName, final String anchorName) { - cpsAdminService.deleteAnchor(dataspaceName, anchorName); + cpsAnchorService.deleteAnchor(dataspaceName, anchorName); return new ResponseEntity<>(HttpStatus.NO_CONTENT); } @@ -229,7 +231,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<AnchorDetails> getAnchor(final String apiVersion, final String dataspaceName, final String anchorName) { - final var anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final var anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final var anchorDetails = cpsRestInputMapper.toAnchorDetails(anchor); return new ResponseEntity<>(anchorDetails, HttpStatus.OK); } @@ -244,7 +246,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<List<AnchorDetails>> getAnchors(final String apiVersion, final String dataspaceName) { - final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName); + final Collection<Anchor> anchors = cpsAnchorService.getAnchors(dataspaceName); final List<AnchorDetails> anchorDetails = anchors.stream().map(cpsRestInputMapper::toAnchorDetails) .collect(Collectors.toList()); return new ResponseEntity<>(anchorDetails, HttpStatus.OK); @@ -252,7 +254,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<List<DataspaceDetails>> getAllDataspaces(final String apiVersion) { - final Collection<Dataspace> dataspaces = cpsAdminService.getAllDataspaces(); + final Collection<Dataspace> dataspaces = cpsDataspaceService.getAllDataspaces(); final List<DataspaceDetails> dataspaceDetails = dataspaces.stream().map(cpsRestInputMapper::toDataspaceDetails) .collect(Collectors.toList()); return new ResponseEntity<>(dataspaceDetails, HttpStatus.OK); @@ -260,7 +262,7 @@ public class AdminRestController implements CpsAdminApi { @Override public ResponseEntity<DataspaceDetails> getDataspace(final String apiVersion, final String dataspaceName) { - final Dataspace dataspace = cpsAdminService.getDataspace(dataspaceName); + final Dataspace dataspace = cpsDataspaceService.getDataspace(dataspaceName); final DataspaceDetails dataspaceDetails = cpsRestInputMapper.toDataspaceDetails(dataspace); return new ResponseEntity<>(dataspaceDetails, HttpStatus.OK); } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy index f81efd6698..81ac511a9f 100755 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/controller/AdminRestControllerSpec.groovy @@ -23,6 +23,8 @@ package org.onap.cps.rest.controller +import org.onap.cps.api.CpsAnchorService + import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get @@ -30,7 +32,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.mapstruct.factory.Mappers -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsDataspaceService import org.onap.cps.api.CpsModuleService import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.SchemaSetInUseException @@ -56,7 +58,10 @@ class AdminRestControllerSpec extends Specification { CpsModuleService mockCpsModuleService = Mock() @SpringBean - CpsAdminService mockCpsAdminService = Mock() + CpsDataspaceService mockCpsDataspaceService = Mock() + + @SpringBean + CpsAnchorService mockCpsAnchorService = Mock() @SpringBean CpsRestInputMapper cpsRestInputMapper = Mappers.getMapper(CpsRestInputMapper) @@ -76,26 +81,27 @@ class AdminRestControllerSpec extends Specification { def 'Create new dataspace with #scenario.'() { when: 'post is invoked' def response = - mvc.perform( - post("/cps/api/${apiVersion}/dataspaces") - .param('dataspace-name', dataspaceName)) - .andReturn().response + mvc.perform( + post("/cps/api/${apiVersion}/dataspaces") + .param('dataspace-name', dataspaceName)) + .andReturn().response then: 'service method is invoked with expected parameters' - 1 * mockCpsAdminService.createDataspace(dataspaceName) + 1 * mockCpsDataspaceService.createDataspace(dataspaceName) and: 'dataspace is create successfully' response.status == HttpStatus.CREATED.value() assert response.getContentAsString() == expectedResponseBody where: 'following cases are tested' - scenario | apiVersion || expectedResponseBody - 'V1 API' | 'v1' || 'my_dataspace' - 'V2 API' | 'v2' || '' - } + scenario | apiVersion || expectedResponseBody + 'V1 API' | 'v1' || 'my_dataspace' + 'V2 API' | 'v2' || '' + } + def 'Create dataspace over existing with same name.'() { given: 'an endpoint' def createDataspaceEndpoint = "$basePath/v1/dataspaces" and: 'the service method throws an exception indicating the dataspace is already defined' def thrownException = new AlreadyDefinedException(dataspaceName, new RuntimeException()) - mockCpsAdminService.createDataspace(dataspaceName) >> { throw thrownException } + mockCpsDataspaceService.createDataspace(dataspaceName) >> { throw thrownException } when: 'post is invoked' def response = mvc.perform( @@ -108,7 +114,7 @@ class AdminRestControllerSpec extends Specification { def 'Get a dataspace.'() { given: 'service method returns a dataspace' - mockCpsAdminService.getDataspace(dataspaceName) >> dataspace + mockCpsDataspaceService.getDataspace(dataspaceName) >> dataspace and: 'an endpoint' def getDataspaceEndpoint = "$basePath/v1/admin/dataspaces/$dataspaceName" when: 'get dataspace API is invoked' @@ -120,7 +126,7 @@ class AdminRestControllerSpec extends Specification { def 'Get all dataspaces.'() { given: 'service method returns all dataspace' - mockCpsAdminService.getAllDataspaces() >> [dataspace, new Dataspace(name: "dataspace-test2")] + mockCpsDataspaceService.getAllDataspaces() >> [dataspace, new Dataspace(name: "dataspace-test2")] and: 'an endpoint' def getAllDataspaceEndpoint = "$basePath/v1/admin/dataspaces" when: 'get all dataspace API is invoked' @@ -317,7 +323,7 @@ class AdminRestControllerSpec extends Specification { .params(requestParams as MultiValueMap)) .andReturn().response then: 'anchor is created successfully' - 1 * mockCpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorName) + 1 * mockCpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorName) assert response.status == HttpStatus.CREATED.value() assert response.getContentAsString() == expectedResponseBody where: 'following cases are tested' @@ -328,7 +334,7 @@ class AdminRestControllerSpec extends Specification { def 'Get existing anchor.'() { given: 'service method returns a list of anchors' - mockCpsAdminService.getAnchors(dataspaceName) >> [anchor] + mockCpsAnchorService.getAnchors(dataspaceName) >> [anchor] and: 'an endpoint' def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors" when: 'get all anchors API is invoked' @@ -340,7 +346,7 @@ class AdminRestControllerSpec extends Specification { def 'Get existing anchor by dataspace and anchor name.'() { given: 'service method returns an anchor' - mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> + mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> new Anchor(name: anchorName, dataspaceName: dataspaceName, schemaSetName: schemaSetName) and: 'an endpoint' def anchorEndpoint = "$basePath/v1/dataspaces/$dataspaceName/anchors/$anchorName" @@ -360,7 +366,7 @@ class AdminRestControllerSpec extends Specification { when: 'delete method is invoked on anchor endpoint' def response = mvc.perform(delete(anchorEndpoint)).andReturn().response then: 'associated service method is invoked with expected parameters' - 1 * mockCpsAdminService.deleteAnchor(dataspaceName, anchorName) + 1 * mockCpsAnchorService.deleteAnchor(dataspaceName, anchorName) and: 'response code indicates success' response.status == HttpStatus.NO_CONTENT.value() } @@ -373,7 +379,7 @@ class AdminRestControllerSpec extends Specification { .param('dataspace-name', dataspaceName)) .andReturn().response then: 'associated service method is invoked with expected parameter' - 1 * mockCpsAdminService.deleteDataspace(dataspaceName) + 1 * mockCpsDataspaceService.deleteDataspace(dataspaceName) and: 'response code indicates success' response.status == HttpStatus.NO_CONTENT.value() } diff --git a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy index 535b83df76..ff2bed4347 100644 --- a/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy +++ b/cps-rest/src/test/groovy/org/onap/cps/rest/exceptions/CpsRestExceptionHandlerSpec.groovy @@ -26,7 +26,8 @@ package org.onap.cps.rest.exceptions import com.fasterxml.jackson.databind.ObjectMapper import groovy.json.JsonSlurper -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService import org.onap.cps.api.CpsQueryService @@ -63,7 +64,10 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder class CpsRestExceptionHandlerSpec extends Specification { @SpringBean - CpsAdminService mockCpsAdminService = Stub() + CpsDataspaceService mockCpsAdminService = Stub() + + @SpringBean + CpsAnchorService mockCpsAnchorService = Stub() @SpringBean CpsModuleService mockCpsModuleService = Stub() @@ -198,7 +202,7 @@ class CpsRestExceptionHandlerSpec extends Specification { */ def setupTestException(exception) { - mockCpsAdminService.getAnchors(_) >> { throw exception } + mockCpsAnchorService.getAnchors(_) >> { throw exception } } def performTestRequest() { diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java b/cps-service/src/main/java/org/onap/cps/api/CpsAnchorService.java index edd052a51c..a247150c15 100755..100644 --- a/cps-service/src/main/java/org/onap/cps/api/CpsAdminService.java +++ b/cps-service/src/main/java/org/onap/cps/api/CpsAnchorService.java @@ -1,9 +1,6 @@ /* - * ============LICENSE_START======================================================= - * Copyright (C) 2020-2023 Nordix Foundation - * Modifications Copyright (C) 2020-2022 Bell Canada. - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022 TechMahindra Ltd. + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,46 +21,10 @@ package org.onap.cps.api; import java.util.Collection; -import org.onap.cps.spi.exceptions.AlreadyDefinedException; import org.onap.cps.spi.exceptions.CpsException; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.Dataspace; -/** - * CPS Admin Service. - */ -public interface CpsAdminService { - - /** - * Create dataspace. - * - * @param dataspaceName dataspace name - * @throws AlreadyDefinedException if dataspace with same name already exists - */ - void createDataspace(String dataspaceName); - - /** - * Delete dataspace. - * - * @param dataspaceName the name of the dataspace to delete - */ - void deleteDataspace(String dataspaceName); - - /** - * Get dataspace by given dataspace name. - * - * @param dataspaceName dataspace name - * @return a dataspace - */ - Dataspace getDataspace(String dataspaceName); - - /** - * Get All Dataspaces. - * - * - * @return a collection of dataspaces - */ - Collection<Dataspace> getAllDataspaces(); +public interface CpsAnchorService { /** * Create an Anchor. diff --git a/cps-service/src/main/java/org/onap/cps/api/CpsDataspaceService.java b/cps-service/src/main/java/org/onap/cps/api/CpsDataspaceService.java new file mode 100644 index 0000000000..7b94604261 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/api/CpsDataspaceService.java @@ -0,0 +1,66 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2020-2023 Nordix Foundation + * Modifications Copyright (C) 2020-2022 Bell Canada. + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2022 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; + +import java.util.Collection; +import org.onap.cps.spi.exceptions.AlreadyDefinedException; +import org.onap.cps.spi.model.Dataspace; + +/** + * CPS Admin Service. + */ +public interface CpsDataspaceService { + + /** + * Create dataspace. + * + * @param dataspaceName dataspace name + * @throws AlreadyDefinedException if dataspace with same name already exists + */ + void createDataspace(String dataspaceName); + + /** + * Delete dataspace. + * + * @param dataspaceName the name of the dataspace to delete + */ + void deleteDataspace(String dataspaceName); + + /** + * Get dataspace by given dataspace name. + * + * @param dataspaceName dataspace name + * @return a dataspace + */ + Dataspace getDataspace(String dataspaceName); + + /** + * Get All Dataspaces. + * + * + * @return a collection of dataspaces + */ + Collection<Dataspace> getAllDataspaces(); + +} diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java index d83ee434de..f09a795a66 100755..100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsAdminServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsAnchorServiceImpl.java @@ -1,9 +1,6 @@ /* - * ============LICENSE_START======================================================= - * Copyright (C) 2020-2023 Nordix Foundation - * Modifications Copyright (C) 2020-2022 Bell Canada. - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022 TechMahindra Ltd. + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,52 +20,25 @@ package org.onap.cps.api.impl; -import java.time.OffsetDateTime; import java.util.Collection; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.onap.cps.api.CpsAdminService; -import org.onap.cps.api.CpsDataService; +import org.onap.cps.api.CpsAnchorService; import org.onap.cps.spi.CpsAdminPersistenceService; +import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.Dataspace; import org.onap.cps.spi.utils.CpsValidator; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; -@Component("CpsAdminServiceImpl") -@RequiredArgsConstructor(onConstructor = @__(@Lazy)) -public class CpsAdminServiceImpl implements CpsAdminService { +@Service +@RequiredArgsConstructor +public class CpsAnchorServiceImpl implements CpsAnchorService { private final CpsAdminPersistenceService cpsAdminPersistenceService; - @Lazy - private final CpsDataService cpsDataService; + private final CpsDataPersistenceService cpsDataPersistenceService; private final CpsValidator cpsValidator; @Override - public void createDataspace(final String dataspaceName) { - cpsValidator.validateNameCharacters(dataspaceName); - cpsAdminPersistenceService.createDataspace(dataspaceName); - } - - @Override - public void deleteDataspace(final String dataspaceName) { - cpsValidator.validateNameCharacters(dataspaceName); - cpsAdminPersistenceService.deleteDataspace(dataspaceName); - } - - @Override - public Dataspace getDataspace(final String dataspaceName) { - cpsValidator.validateNameCharacters(dataspaceName); - return cpsAdminPersistenceService.getDataspace(dataspaceName); - } - - @Override - public Collection<Dataspace> getAllDataspaces() { - return cpsAdminPersistenceService.getAllDataspaces(); - } - - @Override public void createAnchor(final String dataspaceName, final String schemaSetName, final String anchorName) { cpsValidator.validateNameCharacters(dataspaceName, schemaSetName, anchorName); cpsAdminPersistenceService.createAnchor(dataspaceName, schemaSetName, anchorName); @@ -102,7 +72,7 @@ public class CpsAdminServiceImpl implements CpsAdminService { @Override public void deleteAnchor(final String dataspaceName, final String anchorName) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - cpsDataService.deleteDataNodes(dataspaceName, anchorName, OffsetDateTime.now()); + cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName); cpsAdminPersistenceService.deleteAnchor(dataspaceName, anchorName); } @@ -110,7 +80,7 @@ public class CpsAdminServiceImpl implements CpsAdminService { public void deleteAnchors(final String dataspaceName, final Collection<String> anchorNames) { cpsValidator.validateNameCharacters(dataspaceName); cpsValidator.validateNameCharacters(anchorNames); - cpsDataService.deleteDataNodes(dataspaceName, anchorNames, OffsetDateTime.now()); + cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames); cpsAdminPersistenceService.deleteAnchors(dataspaceName, anchorNames); } @@ -123,8 +93,8 @@ public class CpsAdminServiceImpl implements CpsAdminService { @Override public void updateAnchorSchemaSet(final String dataspaceName, - final String anchorName, - final String schemaSetName) { + final String anchorName, + final String schemaSetName) { cpsAdminPersistenceService.updateAnchorSchemaSet(dataspaceName, anchorName, schemaSetName); } } diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java index e74e0ad249..a1bae6a441 100755 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataServiceImpl.java @@ -24,10 +24,6 @@ package org.onap.cps.api.impl; -import static org.onap.cps.notification.Operation.CREATE; -import static org.onap.cps.notification.Operation.DELETE; -import static org.onap.cps.notification.Operation.UPDATE; - import io.micrometer.core.annotation.Timed; import java.io.Serializable; import java.time.OffsetDateTime; @@ -39,12 +35,10 @@ import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsDataService; import org.onap.cps.api.CpsDeltaService; import org.onap.cps.cpspath.parser.CpsPathUtil; -import org.onap.cps.notification.NotificationService; -import org.onap.cps.notification.Operation; import org.onap.cps.spi.CpsDataPersistenceService; import org.onap.cps.spi.FetchDescendantsOption; import org.onap.cps.spi.exceptions.DataValidationException; @@ -68,9 +62,8 @@ public class CpsDataServiceImpl implements CpsDataService { private static final long DEFAULT_LOCK_TIMEOUT_IN_MILLISECONDS = 300L; private final CpsDataPersistenceService cpsDataPersistenceService; - private final CpsAdminService cpsAdminService; + private final CpsAnchorService cpsAnchorService; private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; - private final NotificationService notificationService; private final CpsValidator cpsValidator; private final TimedYangParser timedYangParser; private final CpsDeltaService cpsDeltaService; @@ -87,10 +80,9 @@ public class CpsDataServiceImpl implements CpsDataService { public void saveData(final String dataspaceName, final String anchorName, final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, ROOT_NODE_XPATH, nodeData, contentType); cpsDataPersistenceService.storeDataNodes(dataspaceName, anchorName, dataNodes); - processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, CREATE, observedTimestamp); } @Override @@ -106,10 +98,9 @@ public class CpsDataServiceImpl implements CpsDataService { final String nodeData, final OffsetDateTime observedTimestamp, final ContentType contentType) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, nodeData, contentType); cpsDataPersistenceService.addChildDataNodes(dataspaceName, anchorName, parentNodeXpath, dataNodes); - processDataUpdatedEventAsync(anchor, parentNodeXpath, CREATE, observedTimestamp); } @Override @@ -118,7 +109,7 @@ public class CpsDataServiceImpl implements CpsDataService { public void saveListElements(final String dataspaceName, final String anchorName, final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> listElementDataNodeCollection = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON); if (isRootNodeXpath(parentNodeXpath)) { @@ -127,7 +118,6 @@ public class CpsDataServiceImpl implements CpsDataService { cpsDataPersistenceService.addListElements(dataspaceName, anchorName, parentNodeXpath, listElementDataNodeCollection); } - processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @Override @@ -136,12 +126,11 @@ public class CpsDataServiceImpl implements CpsDataService { public void saveListElementsBatch(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection<String> jsonDataList, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<Collection<DataNode>> listElementDataNodeCollections = buildDataNodes(anchor, parentNodeXpath, jsonDataList, ContentType.JSON); cpsDataPersistenceService.addMultipleLists(dataspaceName, anchorName, parentNodeXpath, listElementDataNodeCollections); - processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @Override @@ -171,13 +160,12 @@ public class CpsDataServiceImpl implements CpsDataService { public void updateNodeLeaves(final String dataspaceName, final String anchorName, final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodesInPatch = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON); final Map<String, Map<String, Serializable>> xpathToUpdatedLeaves = dataNodesInPatch.stream() .collect(Collectors.toMap(DataNode::getXpath, DataNode::getLeaves)); cpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, xpathToUpdatedLeaves); - processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @Override @@ -188,13 +176,12 @@ public class CpsDataServiceImpl implements CpsDataService { final String dataNodeUpdatesAsJson, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodeUpdates = buildDataNodes(anchor, parentNodeXpath, dataNodeUpdatesAsJson, ContentType.JSON); for (final DataNode dataNodeUpdate : dataNodeUpdates) { processDataNodeUpdate(anchor, dataNodeUpdate); } - processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @Override @@ -241,10 +228,9 @@ public class CpsDataServiceImpl implements CpsDataService { final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes); - processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @Override @@ -254,11 +240,9 @@ public class CpsDataServiceImpl implements CpsDataService { final Map<String, String> nodesJsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> dataNodes = buildDataNodes(anchor, nodesJsonData); cpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, dataNodes); - nodesJsonData.keySet().forEach(nodeXpath -> - processDataUpdatedEventAsync(anchor, nodeXpath, UPDATE, observedTimestamp)); } @Override @@ -267,7 +251,7 @@ public class CpsDataServiceImpl implements CpsDataService { public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath, final String jsonData, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); final Collection<DataNode> newListElements = buildDataNodes(anchor, parentNodeXpath, jsonData, ContentType.JSON); replaceListContent(dataspaceName, anchorName, parentNodeXpath, newListElements, observedTimestamp); @@ -279,9 +263,7 @@ public class CpsDataServiceImpl implements CpsDataService { public void replaceListContent(final String dataspaceName, final String anchorName, final String parentNodeXpath, final Collection<DataNode> dataNodes, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); cpsDataPersistenceService.replaceListContent(dataspaceName, anchorName, parentNodeXpath, dataNodes); - processDataUpdatedEventAsync(anchor, parentNodeXpath, UPDATE, observedTimestamp); } @Override @@ -290,9 +272,7 @@ public class CpsDataServiceImpl implements CpsDataService { public void deleteDataNode(final String dataspaceName, final String anchorName, final String dataNodeXpath, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); cpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, dataNodeXpath); - processDataUpdatedEventAsync(anchor, dataNodeXpath, DELETE, observedTimestamp); } @Override @@ -302,9 +282,6 @@ public class CpsDataServiceImpl implements CpsDataService { final Collection<String> dataNodeXpaths, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, dataNodeXpaths); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); - dataNodeXpaths.forEach(dataNodeXpath -> - processDataUpdatedEventAsync(anchor, dataNodeXpath, DELETE, observedTimestamp)); } @Override @@ -313,8 +290,6 @@ public class CpsDataServiceImpl implements CpsDataService { public void deleteDataNodes(final String dataspaceName, final String anchorName, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); - processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, DELETE, observedTimestamp); cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName); } @@ -325,9 +300,6 @@ public class CpsDataServiceImpl implements CpsDataService { final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName); cpsValidator.validateNameCharacters(anchorNames); - for (final Anchor anchor : cpsAdminService.getAnchors(dataspaceName, anchorNames)) { - processDataUpdatedEventAsync(anchor, ROOT_NODE_XPATH, DELETE, observedTimestamp); - } cpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorNames); } @@ -337,9 +309,7 @@ public class CpsDataServiceImpl implements CpsDataService { public void deleteListOrListElement(final String dataspaceName, final String anchorName, final String listNodeXpath, final OffsetDateTime observedTimestamp) { cpsValidator.validateNameCharacters(dataspaceName, anchorName); - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); cpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, listNodeXpath); - processDataUpdatedEventAsync(anchor, listNodeXpath, DELETE, observedTimestamp); } private Collection<DataNode> buildDataNodes(final Anchor anchor, final Map<String, String> nodesJsonData) { @@ -385,16 +355,6 @@ public class CpsDataServiceImpl implements CpsDataService { .collect(Collectors.toList()); } - private void processDataUpdatedEventAsync(final Anchor anchor, final String xpath, - final Operation operation, final OffsetDateTime observedTimestamp) { - try { - notificationService.processDataUpdatedEvent(anchor, xpath, operation, observedTimestamp); - } catch (final Exception exception) { - //If async message can't be queued for notification service, the initial request should not fail. - log.error("Failed to send message to notification service", exception); - } - } - private SchemaContext getSchemaContext(final Anchor anchor) { return yangTextSchemaSourceSetCache .get(anchor.getDataspaceName(), anchor.getSchemaSetName()).getSchemaContext(); diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java new file mode 100644 index 0000000000..a7f5da4874 --- /dev/null +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsDataspaceServiceImpl.java @@ -0,0 +1,64 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2020-2023 Nordix Foundation + * Modifications Copyright (C) 2020-2022 Bell Canada. + * Modifications Copyright (C) 2021 Pantheon.tech + * Modifications Copyright (C) 2022 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.impl; + +import java.util.Collection; +import lombok.RequiredArgsConstructor; +import org.onap.cps.api.CpsDataspaceService; +import org.onap.cps.spi.CpsAdminPersistenceService; +import org.onap.cps.spi.model.Dataspace; +import org.onap.cps.spi.utils.CpsValidator; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CpsDataspaceServiceImpl implements CpsDataspaceService { + + private final CpsAdminPersistenceService cpsAdminPersistenceService; + private final CpsValidator cpsValidator; + + @Override + public void createDataspace(final String dataspaceName) { + cpsValidator.validateNameCharacters(dataspaceName); + cpsAdminPersistenceService.createDataspace(dataspaceName); + } + + @Override + public void deleteDataspace(final String dataspaceName) { + cpsValidator.validateNameCharacters(dataspaceName); + cpsAdminPersistenceService.deleteDataspace(dataspaceName); + } + + @Override + public Dataspace getDataspace(final String dataspaceName) { + cpsValidator.validateNameCharacters(dataspaceName); + return cpsAdminPersistenceService.getDataspace(dataspaceName); + } + + @Override + public Collection<Dataspace> getAllDataspaces() { + return cpsAdminPersistenceService.getAllDataspaces(); + } + +} diff --git a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java index 5337237846..61a4e623af 100644 --- a/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java +++ b/cps-service/src/main/java/org/onap/cps/api/impl/CpsModuleServiceImpl.java @@ -28,7 +28,7 @@ import java.util.Collection; import java.util.Map; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.CpsModuleService; import org.onap.cps.spi.CascadeDeleteAllowed; import org.onap.cps.spi.CpsModulePersistenceService; @@ -49,7 +49,7 @@ public class CpsModuleServiceImpl implements CpsModuleService { private final CpsModulePersistenceService cpsModulePersistenceService; private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; - private final CpsAdminService cpsAdminService; + private final CpsAnchorService cpsAnchorService; private final CpsValidator cpsValidator; private final TimedYangTextSchemaSourceSetBuilder timedYangTextSchemaSourceSetBuilder; @@ -97,12 +97,12 @@ public class CpsModuleServiceImpl implements CpsModuleService { public void deleteSchemaSet(final String dataspaceName, final String schemaSetName, final CascadeDeleteAllowed cascadeDeleteAllowed) { cpsValidator.validateNameCharacters(dataspaceName, schemaSetName); - final Collection<Anchor> anchors = cpsAdminService.getAnchors(dataspaceName, schemaSetName); + final Collection<Anchor> anchors = cpsAnchorService.getAnchors(dataspaceName, schemaSetName); if (!anchors.isEmpty() && isCascadeDeleteProhibited(cascadeDeleteAllowed)) { throw new SchemaSetInUseException(dataspaceName, schemaSetName); } for (final Anchor anchor : anchors) { - cpsAdminService.deleteAnchor(dataspaceName, anchor.getName()); + cpsAnchorService.deleteAnchor(dataspaceName, anchor.getName()); } cpsModulePersistenceService.deleteSchemaSet(dataspaceName, schemaSetName); yangTextSchemaSourceSetCache.removeFromCache(dataspaceName, schemaSetName); @@ -114,9 +114,9 @@ public class CpsModuleServiceImpl implements CpsModuleService { public void deleteSchemaSetsWithCascade(final String dataspaceName, final Collection<String> schemaSetNames) { cpsValidator.validateNameCharacters(dataspaceName); cpsValidator.validateNameCharacters(schemaSetNames); - final Collection<String> anchorNames = cpsAdminService.getAnchors(dataspaceName, schemaSetNames) + final Collection<String> anchorNames = cpsAnchorService.getAnchors(dataspaceName, schemaSetNames) .stream().map(Anchor::getName).collect(Collectors.toSet()); - cpsAdminService.deleteAnchors(dataspaceName, anchorNames); + cpsAnchorService.deleteAnchors(dataspaceName, anchorNames); cpsModulePersistenceService.deleteUnusedYangResourceModules(); cpsModulePersistenceService.deleteSchemaSets(dataspaceName, schemaSetNames); for (final String schemaSetName : schemaSetNames) { diff --git a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java b/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java deleted file mode 100644 index 696fd60f8c..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/CpsDataUpdatedEventFactory.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021-2022 Bell Canada. - * Modifications Copyright (c) 2022-2023 Nordix Foundation - * Modifications Copyright (C) 2023 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.notification; - -import java.net.URI; -import java.net.URISyntaxException; -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; -import java.util.UUID; -import lombok.AllArgsConstructor; -import lombok.SneakyThrows; -import org.onap.cps.api.CpsDataService; -import org.onap.cps.event.model.Content; -import org.onap.cps.event.model.CpsDataUpdatedEvent; -import org.onap.cps.event.model.Data; -import org.onap.cps.spi.FetchDescendantsOption; -import org.onap.cps.spi.model.Anchor; -import org.onap.cps.spi.model.DataNode; -import org.onap.cps.utils.DataMapUtils; -import org.onap.cps.utils.PrefixResolver; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Component; - -@Component -@AllArgsConstructor(onConstructor = @__(@Lazy)) -public class CpsDataUpdatedEventFactory { - - private static final DateTimeFormatter DATE_TIME_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - - @Lazy - private final CpsDataService cpsDataService; - - @Lazy - private final PrefixResolver prefixResolver; - - /** - * Generates CPS Data Updated event. If observedTimestamp is not provided, then current timestamp is used. - * - * @param anchor anchor - * @param observedTimestamp observedTimestamp - * @param operation operation - * @return CpsDataUpdatedEvent - */ - public CpsDataUpdatedEvent createCpsDataUpdatedEvent(final Anchor anchor, - final OffsetDateTime observedTimestamp, final Operation operation) { - final var dataNode = (operation == Operation.DELETE) ? null : - cpsDataService.getDataNodes(anchor.getDataspaceName(), anchor.getName(), - "/", FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS).iterator().next(); - return toCpsDataUpdatedEvent(anchor, dataNode, observedTimestamp, operation); - } - - @SneakyThrows(URISyntaxException.class) - private CpsDataUpdatedEvent toCpsDataUpdatedEvent(final Anchor anchor, - final DataNode dataNode, - final OffsetDateTime observedTimestamp, - final Operation operation) { - final CpsDataUpdatedEvent cpsDataUpdatedEvent = new CpsDataUpdatedEvent(); - cpsDataUpdatedEvent.withContent(createContent(anchor, dataNode, observedTimestamp, operation)); - cpsDataUpdatedEvent.withId(UUID.randomUUID().toString()); - cpsDataUpdatedEvent.withSchema(new URI("urn:cps:org.onap.cps:data-updated-event-schema:v1")); - cpsDataUpdatedEvent.withSource(new URI("urn:cps:org.onap.cps")); - cpsDataUpdatedEvent.withType("org.onap.cps.data-updated-event"); - return cpsDataUpdatedEvent; - } - - private Data createData(final DataNode dataNode, final String prefix) { - final Data data = new Data(); - DataMapUtils.toDataMapWithIdentifier(dataNode, prefix).forEach(data::setAdditionalProperty); - return data; - } - - private Content createContent(final Anchor anchor, final DataNode dataNode, - final OffsetDateTime observedTimestamp, final Operation operation) { - final var content = new Content(); - content.withAnchorName(anchor.getName()); - content.withDataspaceName(anchor.getDataspaceName()); - content.withSchemaSetName(anchor.getSchemaSetName()); - content.withOperation(Content.Operation.fromValue(operation.name())); - content.withObservedTimestamp( - DATE_TIME_FORMATTER.format(observedTimestamp == null ? OffsetDateTime.now() : observedTimestamp)); - if (dataNode != null) { - final String prefix = prefixResolver.getPrefix(anchor, dataNode.getXpath()); - content.withData(createData(dataNode, prefix)); - } - return content; - } -} diff --git a/cps-service/src/main/java/org/onap/cps/notification/KafkaProducerListener.java b/cps-service/src/main/java/org/onap/cps/notification/KafkaProducerListener.java deleted file mode 100644 index f4b68c0699..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/KafkaProducerListener.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification; - -import lombok.extern.slf4j.Slf4j; -import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.clients.producer.RecordMetadata; -import org.springframework.kafka.support.ProducerListener; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class KafkaProducerListener<K, V> implements ProducerListener<K, V> { - - private NotificationErrorHandler notificationErrorHandler; - - public KafkaProducerListener(final NotificationErrorHandler notificationErrorHandler) { - this.notificationErrorHandler = notificationErrorHandler; - } - - @Override - public void onSuccess(final ProducerRecord<K, V> producerRecord, final RecordMetadata recordMetadata) { - log.debug("Message sent to event-bus topic :'{}' with body : {} ", producerRecord.topic(), - producerRecord.value()); - } - - @Override - public void onError(final ProducerRecord<K, V> producerRecord, - final RecordMetadata recordMetadata, - final Exception exception) { - notificationErrorHandler.onException("Failed to send message to message bus", - exception, producerRecord, recordMetadata); - } - -} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationErrorHandler.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationErrorHandler.java deleted file mode 100644 index eef028d5f3..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/NotificationErrorHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -public class NotificationErrorHandler { - - void onException(final Exception exception, final Object... context) { - onException("Failed to process", exception, context); - } - - void onException(final String message, final Exception exception, final Object... context) { - log.error("{} \n Error cause: {} \n Error context: {}", - message, - exception.getCause() != null ? exception.getCause().toString() : exception.getMessage(), - context, - exception); - } -} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java deleted file mode 100644 index b8a7144b3d..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/NotificationProperties.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification; - -import jakarta.validation.constraints.NotNull; -import java.util.Collections; -import java.util.Map; -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -@ConfigurationProperties(prefix = "notification.data-updated") -@Component -@Data -@Validated -public class NotificationProperties { - - @NotNull - private String topic; - private Map<String, String> filters = Collections.emptyMap(); - - @Value("${notification.enabled:true}") - private boolean enabled; -} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationPublisher.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationPublisher.java deleted file mode 100644 index 2d87488245..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/NotificationPublisher.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021 Bell Canada. - * ================================================================================ - * 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.notification; - -import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.onap.cps.event.model.CpsDataUpdatedEvent; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.kafka.core.KafkaTemplate; -import org.springframework.stereotype.Component; - -@Component -@Slf4j -public class NotificationPublisher { - - private KafkaTemplate<String, CpsDataUpdatedEvent> kafkaTemplate; - private String topicName; - - /** - * Create an instance of Notification Publisher. - * - * @param kafkaTemplate kafkaTemplate is send event using kafka - * @param topicName topic, to which cpsDataUpdatedEvent is sent, is provided by setting - * 'notification.data-updated.topic' in the application properties - */ - @Autowired - public NotificationPublisher( - final KafkaTemplate<String, CpsDataUpdatedEvent> kafkaTemplate, - final @Value("${notification.data-updated.topic}") String topicName) { - this.kafkaTemplate = kafkaTemplate; - this.topicName = topicName; - } - - /** - * Send event to Kafka with correct message key. - * - * @param cpsDataUpdatedEvent event to be sent to kafka - */ - public void sendNotification(@NonNull final CpsDataUpdatedEvent cpsDataUpdatedEvent) { - final var messageKey = cpsDataUpdatedEvent.getContent().getDataspaceName() + "," - + cpsDataUpdatedEvent.getContent().getAnchorName(); - log.debug("Data Updated event is being sent with messageKey: '{}' & body : {} ", - messageKey, cpsDataUpdatedEvent); - kafkaTemplate.send(topicName, messageKey, cpsDataUpdatedEvent); - } - -} diff --git a/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java b/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java deleted file mode 100644 index c29d042293..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/NotificationService.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021-2022 Bell Canada. - * Modifications Copyright (C) 2022-2023 Nordix Foundation - * ================================================================================ - * 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.notification; - -import jakarta.annotation.PostConstruct; -import java.time.OffsetDateTime; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Future; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.onap.cps.api.CpsAdminService; -import org.onap.cps.spi.model.Anchor; -import org.springframework.scheduling.annotation.Async; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -@RequiredArgsConstructor -public class NotificationService { - - private final NotificationProperties notificationProperties; - private final NotificationPublisher notificationPublisher; - private final CpsDataUpdatedEventFactory cpsDataUpdatedEventFactory; - private final NotificationErrorHandler notificationErrorHandler; - private final CpsAdminService cpsAdminService; - private List<Pattern> dataspacePatterns; - - @PostConstruct - public void init() { - log.info("Notification Properties {}", notificationProperties); - this.dataspacePatterns = getDataspaceFilterPatterns(notificationProperties); - } - - private List<Pattern> getDataspaceFilterPatterns(final NotificationProperties notificationProperties) { - if (notificationProperties.isEnabled()) { - return Arrays.stream(notificationProperties.getFilters() - .getOrDefault("enabled-dataspaces", "") - .split(",")) - .map(filterPattern -> Pattern.compile(filterPattern, Pattern.CASE_INSENSITIVE)) - .collect(Collectors.toList()); - } else { - return Collections.emptyList(); - } - } - - /** - * Process Data Updated Event and publishes the notification. - * - * @param anchor anchor - * @param xpath xpath of changed data node - * @param operation operation - * @param observedTimestamp observedTimestamp - * @return future - */ - @Async("notificationExecutor") - public Future<Void> processDataUpdatedEvent(final Anchor anchor, final String xpath, final Operation operation, - final OffsetDateTime observedTimestamp) { - - log.debug("process data updated event for anchor '{}'", anchor); - try { - if (shouldSendNotification(anchor.getDataspaceName())) { - final var cpsDataUpdatedEvent = - cpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, - observedTimestamp, getRootNodeOperation(xpath, operation)); - log.debug("data updated event to be published {}", cpsDataUpdatedEvent); - notificationPublisher.sendNotification(cpsDataUpdatedEvent); - } - } catch (final Exception exception) { - /* All the exceptions are handled to not to propagate it to caller. - CPS operation should not fail if sending event fails for any reason. - */ - notificationErrorHandler.onException("Failed to process cps-data-updated-event.", - exception, anchor, xpath, operation); - } - return CompletableFuture.completedFuture(null); - } - - /* - Add more complex rules based on dataspace and anchor later - */ - private boolean shouldSendNotification(final String dataspaceName) { - - return notificationProperties.isEnabled() - && dataspacePatterns.stream() - .anyMatch(pattern -> pattern.matcher(dataspaceName).find()); - } - - private Operation getRootNodeOperation(final String xpath, final Operation operation) { - return isRootXpath(xpath) || isRootContainerNodeXpath(xpath) ? operation : Operation.UPDATE; - } - - private static boolean isRootXpath(final String xpath) { - return "/".equals(xpath) || "".equals(xpath); - } - - private static boolean isRootContainerNodeXpath(final String xpath) { - return 0 == xpath.lastIndexOf('/'); - } - -} diff --git a/cps-service/src/main/java/org/onap/cps/notification/Operation.java b/cps-service/src/main/java/org/onap/cps/notification/Operation.java deleted file mode 100644 index 83e1ccf79f..0000000000 --- a/cps-service/src/main/java/org/onap/cps/notification/Operation.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2022 Bell Canada. - * ================================================================================ - * 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.notification; - -public enum Operation { - CREATE, - UPDATE, - DELETE -} diff --git a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java index d58ddf4fa9..35dc7347b2 100644 --- a/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java +++ b/cps-service/src/main/java/org/onap/cps/utils/PrefixResolver.java @@ -26,7 +26,7 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import lombok.RequiredArgsConstructor; -import org.onap.cps.api.CpsAdminService; +import org.onap.cps.api.CpsAnchorService; import org.onap.cps.api.impl.YangTextSchemaSourceSetCache; import org.onap.cps.cache.AnchorDataCacheEntry; import org.onap.cps.cpspath.parser.CpsPathPrefixType; @@ -48,7 +48,7 @@ public class PrefixResolver { private static final String CACHE_ENTRY_PROPERTY_NAME = "prefixPerContainerName"; - private final CpsAdminService cpsAdminService; + private final CpsAnchorService cpsAnchorService; private final YangTextSchemaSourceSetCache yangTextSchemaSourceSetCache; @@ -63,7 +63,7 @@ public class PrefixResolver { * @return the prefix of the module the top level element of given xpath */ public String getPrefix(final String dataspaceName, final String anchorName, final String xpath) { - final Anchor anchor = cpsAdminService.getAnchor(dataspaceName, anchorName); + final Anchor anchor = cpsAnchorService.getAnchor(dataspaceName, anchorName); return getPrefix(anchor, xpath); } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy index 12564fb6d4..3546b81671 100755..100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAdminServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsAnchorServiceImplSpec.groovy @@ -1,9 +1,6 @@ /* - * ============LICENSE_START======================================================= - * Copyright (C) 2020-2023 Nordix Foundation - * Modifications Copyright (C) 2020-2022 Bell Canada. - * Modifications Copyright (C) 2021 Pantheon.tech - * Modifications Copyright (C) 2022 TechMahindra Ltd. + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,29 +20,20 @@ package org.onap.cps.api.impl -import org.onap.cps.api.CpsDataService import org.onap.cps.spi.CpsAdminPersistenceService +import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.exceptions.ModuleNamesNotFoundException import org.onap.cps.spi.model.Anchor -import org.onap.cps.spi.model.Dataspace import org.onap.cps.spi.utils.CpsValidator import spock.lang.Specification -import java.time.OffsetDateTime -class CpsAdminServiceImplSpec extends Specification { +class CpsAnchorServiceImplSpec extends Specification { + def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService) - def mockCpsDataService = Mock(CpsDataService) + def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) def mockCpsValidator = Mock(CpsValidator) - def objectUnderTest = new CpsAdminServiceImpl(mockCpsAdminPersistenceService, mockCpsDataService,mockCpsValidator) - def 'Create dataspace method invokes persistence service.'() { - when: 'create dataspace method is invoked' - objectUnderTest.createDataspace('someDataspace') - then: 'the persistence service method is invoked with same parameters' - 1 * mockCpsAdminPersistenceService.createDataspace('someDataspace') - and: 'the CpsValidator is called on the dataspaceName' - 1 * mockCpsValidator.validateNameCharacters('someDataspace') - } + def objectUnderTest = new CpsAnchorServiceImpl(mockCpsAdminPersistenceService, mockCpsDataPersistenceService, mockCpsValidator) def 'Create anchor method invokes persistence service.'() { when: 'create anchor method is invoked' @@ -105,29 +93,13 @@ class CpsAdminServiceImplSpec extends Specification { 1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someAnchor') } - def 'Retrieve dataspace.'() { - given: 'a dataspace is already created' - def dataspace = new Dataspace(name: "someDataspace") - mockCpsAdminPersistenceService.getDataspace('someDataspace') >> dataspace - expect: 'the dataspace provided by persistence service is returned as result' - assert objectUnderTest.getDataspace('someDataspace') == dataspace - } - - def 'Retrieve all dataspaces.'() { - given: 'that all given dataspaces are already created' - def dataspaces = [new Dataspace(name: "test-dataspace1"), new Dataspace(name: "test-dataspace2")] - mockCpsAdminPersistenceService.getAllDataspaces() >> dataspaces - expect: 'the dataspace provided by persistence service is returned as result' - assert objectUnderTest.getAllDataspaces() == dataspaces - } - def 'Delete anchor.'() { when: 'delete anchor is invoked' objectUnderTest.deleteAnchor('someDataspace','someAnchor') then: 'delete data nodes is invoked on the data service with expected parameters' - 1 * mockCpsDataService.deleteDataNodes('someDataspace','someAnchor', _ as OffsetDateTime ) + 1 * mockCpsDataPersistenceService.deleteDataNodes('someDataspace','someAnchor') and: 'the persistence service method is invoked with same parameters to delete anchor' - 1 * mockCpsAdminPersistenceService.deleteAnchor('someDataspace','someAnchor') + 1 * mockCpsAdminPersistenceService.deleteAnchor('someDataspace','someAnchor') and: 'the CpsValidator is called on the dataspaceName, anchorName' 1 * mockCpsValidator.validateNameCharacters('someDataspace', 'someAnchor') } @@ -136,7 +108,7 @@ class CpsAdminServiceImplSpec extends Specification { when: 'delete anchors is invoked' objectUnderTest.deleteAnchors('someDataspace', ['anchor1', 'anchor2']) then: 'delete data nodes is invoked on the data service with expected parameters' - 1 * mockCpsDataService.deleteDataNodes('someDataspace', _ as Collection<String>, _ as OffsetDateTime) + 1 * mockCpsDataPersistenceService.deleteDataNodes('someDataspace', _ as Collection<String>) and: 'the persistence service method is invoked with same parameters to delete anchor' 1 * mockCpsAdminPersistenceService.deleteAnchors('someDataspace',_ as Collection<String>) and: 'the CpsValidator is called on the dataspace name and anchor names' @@ -157,7 +129,7 @@ class CpsAdminServiceImplSpec extends Specification { def 'Query all anchors with Module Names Not Found Exception in persistence layer.'() { given: 'the persistence layer throws a Module Names Not Found Exception' - def originalException = new ModuleNamesNotFoundException('exception-ds', [ 'm1', 'm2']) + def originalException = new ModuleNamesNotFoundException('exception-ds', ['m1', 'm2']) mockCpsAdminPersistenceService.queryAnchors(*_) >> { throw originalException} when: 'attempt query anchors' objectUnderTest.queryAnchorNames('some-dataspace-name', []) @@ -170,19 +142,11 @@ class CpsAdminServiceImplSpec extends Specification { assert thrownUp.details.contains('m2') } - def 'Delete dataspace.'() { - when: 'delete dataspace is invoked' - objectUnderTest.deleteDataspace('someDataspace') - then: 'associated persistence service method is invoked with correct parameter' - 1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace') - and: 'the CpsValidator is called on the dataspaceName' - 1 * mockCpsValidator.validateNameCharacters('someDataspace') - } - def 'Update anchor schema set.'() { when: 'update anchor is invoked' objectUnderTest.updateAnchorSchemaSet('someDataspace', 'someAnchor', 'someSchemaSetName') then: 'associated persistence service method is invoked with correct parameter' 1 * mockCpsAdminPersistenceService.updateAnchorSchemaSet('someDataspace', 'someAnchor', 'someSchemaSetName') } + } diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy index a914598521..77e15c320e 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataServiceImplSpec.groovy @@ -24,10 +24,8 @@ package org.onap.cps.api.impl import org.onap.cps.TestUtils -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDeltaService -import org.onap.cps.notification.NotificationService -import org.onap.cps.notification.Operation import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.ConcurrencyException @@ -38,7 +36,6 @@ import org.onap.cps.spi.exceptions.SessionTimeoutException import org.onap.cps.spi.model.Anchor import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.DataNodeBuilder -import org.onap.cps.spi.model.DeltaReportBuilder import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.ContentType import org.onap.cps.utils.TimedYangParser @@ -46,27 +43,24 @@ import org.onap.cps.yang.YangTextSchemaSourceSet import org.onap.cps.yang.YangTextSchemaSourceSetBuilder import spock.lang.Shared import spock.lang.Specification - import java.time.OffsetDateTime import java.util.stream.Collectors class CpsDataServiceImplSpec extends Specification { def mockCpsDataPersistenceService = Mock(CpsDataPersistenceService) - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsAnchorService = Mock(CpsAnchorService) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) - def mockNotificationService = Mock(NotificationService) def mockCpsValidator = Mock(CpsValidator) def timedYangParser = new TimedYangParser() def mockCpsDeltaService = Mock(CpsDeltaService); - def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAdminService, - mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser, mockCpsDeltaService) + def objectUnderTest = new CpsDataServiceImpl(mockCpsDataPersistenceService, mockCpsAnchorService, + mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService) def setup() { - - mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor - mockCpsAdminService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1 - mockCpsAdminService.getAnchor(dataspaceName, ANCHOR_NAME_2) >> anchor2 + mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >> anchor + mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_1) >> anchor1 + mockCpsAnchorService.getAnchor(dataspaceName, ANCHOR_NAME_2) >> anchor2 } @Shared @@ -92,8 +86,6 @@ class CpsDataServiceImplSpec extends Specification { { dataNode -> dataNode.xpath[0] == '/test-tree' }) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.CREATE, observedTimestamp) where: 'given parameters' scenario | dataFile | contentType 'json' | 'test-tree.json' | ContentType.JSON @@ -115,18 +107,6 @@ class CpsDataServiceImplSpec extends Specification { 'invalid xml' | '<invalid xml' | ContentType.XML || 'Failed to parse xml data' } - def 'Saving #scenarioDesired data exception during notification.'() { - given: 'schema set for given anchor and dataspace references test-tree model' - setupSchemaSetMocks('test-tree.yang') - and: 'the notification service throws an exception' - mockNotificationService.processDataUpdatedEvent(*_) >> { throw new RuntimeException('to be ignored')} - when: 'save data method is invoked with test-tree json data' - def data = TestUtils.getResourceFileContent('test-tree.json') - objectUnderTest.saveData(dataspaceName, anchorName, data, observedTimestamp) - then: 'the exception is ignored' - noExceptionThrown() - } - def 'Saving list element data fragment under Root node.'() { given: 'schema set for given anchor and dataspace references bookstore model' setupSchemaSetMocks('bookstore.yang') @@ -145,8 +125,6 @@ class CpsDataServiceImplSpec extends Specification { ) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.UPDATE, observedTimestamp) } def 'Saving child data fragment under existing node.'() { @@ -160,8 +138,6 @@ class CpsDataServiceImplSpec extends Specification { { dataNode -> dataNode.xpath[0] == '/test-tree/branch[@name=\'New\']' }) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.CREATE, observedTimestamp) } def 'Saving list element data fragment under existing node.'() { @@ -182,8 +158,6 @@ class CpsDataServiceImplSpec extends Specification { ) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp) } def 'Saving collection of a batch with data fragment under existing node.'() { @@ -202,8 +176,6 @@ class CpsDataServiceImplSpec extends Specification { assert listOfXpaths.containsAll(['/test-tree/branch[@name=\'B\']','/test-tree/branch[@name=\'A\']']) } } - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp) } def 'Saving empty list element data fragment.'() { @@ -266,8 +238,6 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[0] == expectedNodeXpath}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) where: 'following parameters were used' scenario | parentNodeXpath | jsonData || expectedNodeXpath 'top level node' | '/' | '{"test-tree": {"branch": []}}' || '/test-tree' @@ -300,8 +270,6 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.batchUpdateDataLeaves(dataspaceName, anchorName, {dataNode -> dataNode.keySet()[index] == expectedNodeXpath}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) where: 'the following parameters were used' index | expectedNodeXpath 0 | '/first-container' @@ -325,8 +293,6 @@ class CpsDataServiceImplSpec extends Specification { .iterator().next() == "/bookstore/categories[@code='01']/books[@title='new']"}) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'the data updated event is sent to the notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/bookstore', Operation.UPDATE, observedTimestamp) } def 'Replace data node using singular data node: #scenario.'() { @@ -337,8 +303,6 @@ class CpsDataServiceImplSpec extends Specification { then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, { dataNode -> dataNode.xpath == expectedNodeXpath}) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, parentNodeXpath, Operation.UPDATE, observedTimestamp) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) where: 'following parameters were used' @@ -356,10 +320,6 @@ class CpsDataServiceImplSpec extends Specification { then: 'the persistence service method is invoked with correct parameters' 1 * mockCpsDataPersistenceService.updateDataNodesAndDescendants(dataspaceName, anchorName, { dataNode -> dataNode.xpath == expectedNodeXpath}) - and: 'data updated event is sent to notification service' - nodesJsonData.keySet().each { - 1 * mockNotificationService.processDataUpdatedEvent(anchor, it, Operation.UPDATE, observedTimestamp) - } and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) where: 'following parameters were used' @@ -399,8 +359,6 @@ class CpsDataServiceImplSpec extends Specification { ) and: 'the CpsValidator is called on the dataspaceName and AnchorName twice' 2 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree', Operation.UPDATE, observedTimestamp) } def 'Replace whole list content with empty list element.'() { @@ -420,8 +378,6 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteListDataNode(dataspaceName, anchorName, '/test-tree/branch') and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/test-tree/branch', Operation.DELETE, observedTimestamp) } def 'Delete multiple list elements under existing node.'() { @@ -431,8 +387,6 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName, ['/test-tree/branch[@name="A"]', '/test-tree/branch[@name="B"]']) and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'two data updated events are sent to notification service' - 2 * mockNotificationService.processDataUpdatedEvent(anchor, _, Operation.DELETE, observedTimestamp) } def 'Delete data node under anchor and dataspace.'() { @@ -442,16 +396,12 @@ class CpsDataServiceImplSpec extends Specification { 1 * mockCpsDataPersistenceService.deleteDataNode(dataspaceName, anchorName, '/data-node') and: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) - and: 'data updated event is sent to notification service' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/data-node', Operation.DELETE, observedTimestamp) } def 'Delete all data nodes for a given anchor and dataspace.'() { when: 'delete data nodes method is invoked with correct parameters' objectUnderTest.deleteDataNodes(dataspaceName, anchorName, observedTimestamp) - then: 'data updated event is sent to notification service before the delete' - 1 * mockNotificationService.processDataUpdatedEvent(anchor, '/', Operation.DELETE, observedTimestamp) - and: 'the CpsValidator is called on the dataspaceName and AnchorName' + then: 'the CpsValidator is called on the dataspaceName and AnchorName' 1 * mockCpsValidator.validateNameCharacters(dataspaceName, anchorName) and: 'the persistence service method is invoked with the correct parameters' 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, anchorName) @@ -474,14 +424,12 @@ class CpsDataServiceImplSpec extends Specification { def 'Delete all data nodes for given dataspace and multiple anchors.'() { given: 'schema set for given anchors and dataspace references test tree model' setupSchemaSetMocks('test-tree.yang') - mockCpsAdminService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >> + mockCpsAnchorService.getAnchors(dataspaceName, ['anchor1', 'anchor2']) >> [new Anchor(name: 'anchor1', dataspaceName: dataspaceName), new Anchor(name: 'anchor2', dataspaceName: dataspaceName)] when: 'delete data node method is invoked with correct parameters' objectUnderTest.deleteDataNodes(dataspaceName, ['anchor1', 'anchor2'], observedTimestamp) - then: 'data updated events are sent to notification service before the delete' - 2 * mockNotificationService.processDataUpdatedEvent(_, '/', Operation.DELETE, observedTimestamp) - and: 'the CpsValidator is called on the dataspace name and the anchor names' + then: 'the CpsValidator is called on the dataspace name and the anchor names' 2 * mockCpsValidator.validateNameCharacters(_) and: 'the persistence service method is invoked with the correct parameters' 1 * mockCpsDataPersistenceService.deleteDataNodes(dataspaceName, _ as Collection<String>) diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy new file mode 100644 index 0000000000..8e17594bd1 --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsDataspaceServiceImplSpec.groovy @@ -0,0 +1,67 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * 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.impl + +import org.onap.cps.spi.CpsAdminPersistenceService +import org.onap.cps.spi.model.Dataspace +import org.onap.cps.spi.utils.CpsValidator +import spock.lang.Specification + +class CpsDataspaceServiceImplSpec extends Specification { + def mockCpsAdminPersistenceService = Mock(CpsAdminPersistenceService) + def mockCpsValidator = Mock(CpsValidator) + def objectUnderTest = new CpsDataspaceServiceImpl(mockCpsAdminPersistenceService,mockCpsValidator) + + def 'Create dataspace method invokes persistence service.'() { + when: 'create dataspace method is invoked' + objectUnderTest.createDataspace('someDataspace') + then: 'the persistence service method is invoked with same parameters' + 1 * mockCpsAdminPersistenceService.createDataspace('someDataspace') + and: 'the CpsValidator is called on the dataspaceName' + 1 * mockCpsValidator.validateNameCharacters('someDataspace') + } + + def 'Retrieve dataspace.'() { + given: 'a dataspace is already created' + def dataspace = new Dataspace(name: "someDataspace") + mockCpsAdminPersistenceService.getDataspace('someDataspace') >> dataspace + expect: 'the dataspace provided by persistence service is returned as result' + assert objectUnderTest.getDataspace('someDataspace') == dataspace + } + + def 'Retrieve all dataspaces.'() { + given: 'that all given dataspaces are already created' + def dataspaces = [new Dataspace(name: "test-dataspace1"), new Dataspace(name: "test-dataspace2")] + mockCpsAdminPersistenceService.getAllDataspaces() >> dataspaces + expect: 'the dataspace provided by persistence service is returned as result' + assert objectUnderTest.getAllDataspaces() == dataspaces + } + + def 'Delete dataspace.'() { + when: 'delete dataspace is invoked' + objectUnderTest.deleteDataspace('someDataspace') + then: 'associated persistence service method is invoked with correct parameter' + 1 * mockCpsAdminPersistenceService.deleteDataspace('someDataspace') + and: 'the CpsValidator is called on the dataspaceName' + 1 * mockCpsValidator.validateNameCharacters('someDataspace') + } + +} diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy index d8edb02abd..d909e27abf 100644 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/CpsModuleServiceImplSpec.groovy @@ -23,11 +23,12 @@ package org.onap.cps.api.impl +import org.onap.cps.api.CpsAnchorService + import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_ALLOWED import static org.onap.cps.spi.CascadeDeleteAllowed.CASCADE_DELETE_PROHIBITED import org.onap.cps.TestUtils -import org.onap.cps.api.CpsAdminService import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.exceptions.DuplicatedYangResourceException import org.onap.cps.spi.exceptions.ModelValidationException @@ -45,12 +46,12 @@ import spock.lang.Specification class CpsModuleServiceImplSpec extends Specification { def mockCpsModulePersistenceService = Mock(CpsModulePersistenceService) - def mockCpsAdminService = Mock(CpsAdminService) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) + def mockCpsAnchorService = Mock(CpsAnchorService) def mockCpsValidator = Mock(CpsValidator) def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder() - def objectUnderTest = new CpsModuleServiceImpl(mockCpsModulePersistenceService, mockYangTextSchemaSourceSetCache, mockCpsAdminService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder) + def objectUnderTest = new CpsModuleServiceImpl(mockCpsModulePersistenceService, mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder) def 'Create schema set.'() { when: 'Create schema set method is invoked' @@ -133,11 +134,11 @@ class CpsModuleServiceImplSpec extends Specification { def 'Delete schema-set when cascade is allowed.'() { given: '#numberOfAnchors anchors are associated with schemaset' def associatedAnchors = createAnchors(numberOfAnchors) - mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset') >> associatedAnchors + mockCpsAnchorService.getAnchors('my-dataspace', 'my-schemaset') >> associatedAnchors when: 'schema set deletion is requested with cascade allowed' objectUnderTest.deleteSchemaSet('my-dataspace', 'my-schemaset', CASCADE_DELETE_ALLOWED) then: 'anchor deletion is called #numberOfAnchors times' - numberOfAnchors * mockCpsAdminService.deleteAnchor('my-dataspace', _) + numberOfAnchors * mockCpsAnchorService.deleteAnchor('my-dataspace', _) and: 'persistence service method is invoked with same parameters' 1 * mockCpsModulePersistenceService.deleteSchemaSet('my-dataspace', 'my-schemaset') and: 'schema set will be removed from the cache' @@ -152,11 +153,11 @@ class CpsModuleServiceImplSpec extends Specification { def 'Delete schema-set when cascade is prohibited.'() { given: 'no anchors are associated with schemaset' - mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset') >> Collections.emptyList() + mockCpsAnchorService.getAnchors('my-dataspace', 'my-schemaset') >> Collections.emptyList() when: 'schema set deletion is requested with cascade allowed' objectUnderTest.deleteSchemaSet('my-dataspace', 'my-schemaset', CASCADE_DELETE_PROHIBITED) then: 'no anchors are deleted' - 0 * mockCpsAdminService.deleteAnchor(_, _) + 0 * mockCpsAnchorService.deleteAnchor(_, _) and: 'persistence service method is invoked with same parameters' 1 * mockCpsModulePersistenceService.deleteSchemaSet('my-dataspace', 'my-schemaset') and: 'schema set will be removed from the cache' @@ -169,7 +170,7 @@ class CpsModuleServiceImplSpec extends Specification { def 'Delete schema-set when cascade is prohibited and schema-set has anchors.'() { given: '2 anchors are associated with schemaset' - mockCpsAdminService.getAnchors('my-dataspace', 'my-schemaset') >> createAnchors(2) + mockCpsAnchorService.getAnchors('my-dataspace', 'my-schemaset') >> createAnchors(2) when: 'schema set deletion is requested with cascade allowed' objectUnderTest.deleteSchemaSet('my-dataspace', 'my-schemaset', CASCADE_DELETE_PROHIBITED) then: 'Schema-Set in Use exception is thrown' @@ -178,11 +179,11 @@ class CpsModuleServiceImplSpec extends Specification { def 'Delete multiple schema-sets when cascade is allowed.'() { given: '#numberOfAnchors anchors are associated with each schemaset' - mockCpsAdminService.getAnchors('my-dataspace', ['my-schemaset1', 'my-schemaset2']) >> createAnchors(numberOfAnchors * 2) + mockCpsAnchorService.getAnchors('my-dataspace', ['my-schemaset1', 'my-schemaset2']) >> createAnchors(numberOfAnchors * 2) when: 'schema set deletion is requested with cascade allowed' objectUnderTest.deleteSchemaSetsWithCascade('my-dataspace', ['my-schemaset1', 'my-schemaset2']) then: 'anchor deletion is called #numberOfAnchors times' - mockCpsAdminService.deleteAnchors('my-dataspace', _) + mockCpsAnchorService.deleteAnchors('my-dataspace', _) and: 'persistence service method is invoked with same parameters' mockCpsModulePersistenceService.deleteSchemaSets('my-dataspace', _) and: 'schema sets will be removed from the cache' diff --git a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy index 1b873ec12b..4782468f19 100755 --- a/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/api/impl/E2ENetworkSliceSpec.groovy @@ -24,9 +24,9 @@ package org.onap.cps.api.impl
import org.onap.cps.TestUtils
-import org.onap.cps.api.CpsAdminService
+import org.onap.cps.api.CpsAnchorService
import org.onap.cps.api.CpsDeltaService
-import org.onap.cps.notification.NotificationService
+import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsDataPersistenceService
import org.onap.cps.spi.CpsModulePersistenceService
import org.onap.cps.spi.model.Anchor
@@ -40,8 +40,7 @@ import spock.lang.Specification class E2ENetworkSliceSpec extends Specification {
def mockModuleStoreService = Mock(CpsModulePersistenceService)
def mockDataStoreService = Mock(CpsDataPersistenceService)
- def mockCpsAdminService = Mock(CpsAdminService)
- def mockNotificationService = Mock(NotificationService)
+ def mockCpsAnchorService = Mock(CpsAnchorService)
def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache)
def mockCpsValidator = Mock(CpsValidator)
def timedYangTextSchemaSourceSetBuilder = new TimedYangTextSchemaSourceSetBuilder()
@@ -49,10 +48,10 @@ class E2ENetworkSliceSpec extends Specification { def mockCpsDeltaService = Mock(CpsDeltaService)
def cpsModuleServiceImpl = new CpsModuleServiceImpl(mockModuleStoreService,
- mockYangTextSchemaSourceSetCache, mockCpsAdminService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
+ mockYangTextSchemaSourceSetCache, mockCpsAnchorService, mockCpsValidator,timedYangTextSchemaSourceSetBuilder)
- def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAdminService,
- mockYangTextSchemaSourceSetCache, mockNotificationService, mockCpsValidator, timedYangParser, mockCpsDeltaService)
+ def cpsDataServiceImpl = new CpsDataServiceImpl(mockDataStoreService, mockCpsAnchorService,
+ mockYangTextSchemaSourceSetCache, mockCpsValidator, timedYangParser, mockCpsDeltaService)
def dataspaceName = 'someDataspace'
def anchorName = 'someAnchor'
@@ -91,7 +90,7 @@ class E2ENetworkSliceSpec extends Specification { and : 'a valid json is provided for the model'
def jsonData = TestUtils.getResourceFileContent('e2e/basic/cps-Cavsta-Data.txt')
and : 'all the further dependencies are mocked '
- mockCpsAdminService.getAnchor(dataspaceName, anchorName) >>
+ mockCpsAnchorService.getAnchor(dataspaceName, anchorName) >>
new Anchor().builder().name(anchorName).schemaSetName(schemaSetName).dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get(dataspaceName, schemaSetName) >>
YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
@@ -124,7 +123,7 @@ class E2ENetworkSliceSpec extends Specification { and : 'a valid json is provided for the model'
def jsonData = TestUtils.getResourceFileContent('e2e/basic/cps-ran-inventory-data.json')
and : 'all the further dependencies are mocked '
- mockCpsAdminService.getAnchor('someDataspace', 'someAnchor') >>
+ mockCpsAnchorService.getAnchor('someDataspace', 'someAnchor') >>
new Anchor().builder().name('someAnchor').schemaSetName('someSchemaSet').dataspaceName(dataspaceName).build()
mockYangTextSchemaSourceSetCache.get('someDataspace', 'someSchemaSet') >> YangTextSchemaSourceSetBuilder.of(yangResourcesNameToContentMap)
mockModuleStoreService.getYangSchemaResources('someDataspace', 'someSchemaSet') >> schemaContext
diff --git a/cps-service/src/test/groovy/org/onap/cps/config/AsyncConfigSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/config/AsyncConfigSpec.groovy new file mode 100644 index 0000000000..9f4e81aabc --- /dev/null +++ b/cps-service/src/test/groovy/org/onap/cps/config/AsyncConfigSpec.groovy @@ -0,0 +1,46 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * 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.config + +import spock.lang.Specification + +class AsyncConfigSpec extends Specification { + + def objectUnderTest = new AsyncConfig() + + def 'Create Async Config and validate it'() { + when: 'we set some test properties to tune taskexecutor' + objectUnderTest.setCorePoolSize(5) + objectUnderTest.setMaxPoolSize(50) + objectUnderTest.setQueueCapacity(100) + objectUnderTest.setThreadNamePrefix('Test-') + objectUnderTest.setWaitForTasksToCompleteOnShutdown(true) + then: 'we can instantiate a Async Config object' + assert objectUnderTest != null + and: 'taskexector is configured with correct properties' + def tasExecutor = objectUnderTest.getThreadAsyncExecutorForNotification() + assert tasExecutor.properties['corePoolSize'] == 5 + assert tasExecutor.properties['maxPoolSize'] == 50 + assert tasExecutor.properties['queueCapacity'] == 100 + assert tasExecutor.properties['keepAliveSeconds'] == 60 + assert tasExecutor.properties['threadNamePrefix'] == 'Test-' + } +} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy deleted file mode 100644 index 49f4bf3850..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/CpsDataUpdatedEventFactorySpec.groovy +++ /dev/null @@ -1,142 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021-2022 Bell Canada. - * Modifications Copyright (c) 2022-2023 Nordix Foundation - * Modifications Copyright (C) 2023 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.notification - -import org.onap.cps.spi.model.DataNode - -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter -import org.onap.cps.utils.DateTimeUtility -import org.onap.cps.utils.PrefixResolver -import org.onap.cps.api.CpsDataService -import org.onap.cps.event.model.Content -import org.onap.cps.event.model.Data -import org.onap.cps.spi.FetchDescendantsOption -import org.onap.cps.spi.model.Anchor -import org.onap.cps.spi.model.DataNodeBuilder -import org.springframework.util.StringUtils -import spock.lang.Specification - -class CpsDataUpdatedEventFactorySpec extends Specification { - - def mockCpsDataService = Mock(CpsDataService) - - def mockPrefixResolver = Mock(PrefixResolver) - - def objectUnderTest = new CpsDataUpdatedEventFactory(mockCpsDataService, mockPrefixResolver) - - def dateTimeFormat = 'yyyy-MM-dd\'T\'HH:mm:ss.SSSZ' - - def 'Create a CPS data updated event successfully: #scenario'() { - given: 'an anchor which has been updated' - def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name') - and: 'cps data service returns the data node details' - def xpath = '/xpath' - def dataNode = new DataNodeBuilder().withXpath(xpath).withLeaves(['leafName': 'leafValue']).build() - mockCpsDataService.getDataNodes( - 'my-dataspace', 'my-anchorname', '/', FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS) >> [dataNode] - when: 'CPS data updated event is created' - def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(anchor, - DateTimeUtility.toOffsetDateTime(inputObservedTimestamp), Operation.CREATE) - then: 'CPS data updated event is created with correct envelope' - with(cpsDataUpdatedEvent) { - type == 'org.onap.cps.data-updated-event' - source == new URI('urn:cps:org.onap.cps') - schema == new URI('urn:cps:org.onap.cps:data-updated-event-schema:v1') - StringUtils.hasText(id) - content != null - } - and: 'correct content' - with(cpsDataUpdatedEvent.content) { - assert isExpectedDateTimeFormat(observedTimestamp): "$observedTimestamp is not in $dateTimeFormat format" - if (inputObservedTimestamp != null) - assert observedTimestamp == inputObservedTimestamp - else - assert OffsetDateTime.now().minusSeconds(20).isBefore( - DateTimeUtility.toOffsetDateTime(observedTimestamp)) - assert anchorName == 'my-anchorname' - assert dataspaceName == 'my-dataspace' - assert schemaSetName == 'my-schemaset-name' - assert operation == Content.Operation.CREATE - assert data == new Data().withAdditionalProperty('xpath', ['leafName': 'leafValue']) - } - where: - scenario | inputObservedTimestamp - 'with observed timestamp -0400' | '2021-01-01T23:00:00.345-0400' - 'with observed timestamp +0400' | '2021-01-01T23:00:00.345+0400' - 'missing observed timestamp' | null - } - - def 'Create a delete CPS data updated event successfully'() { - given: 'an anchor which has been deleted' - def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name') - def deletionTimestamp = '2021-01-01T23:00:00.345-0400' - when: 'a delete root data node event is created' - def cpsDataUpdatedEvent = objectUnderTest.createCpsDataUpdatedEvent(anchor, - DateTimeUtility.toOffsetDateTime(deletionTimestamp), Operation.DELETE) - then: 'CPS data updated event is created with correct envelope' - with(cpsDataUpdatedEvent) { - type == 'org.onap.cps.data-updated-event' - source == new URI('urn:cps:org.onap.cps') - schema == new URI('urn:cps:org.onap.cps:data-updated-event-schema:v1') - StringUtils.hasText(id) - content != null - } - and: 'correct content' - with(cpsDataUpdatedEvent.content) { - assert isExpectedDateTimeFormat(observedTimestamp): "$observedTimestamp is not in $dateTimeFormat format" - assert observedTimestamp == deletionTimestamp - assert anchorName == 'my-anchorname' - assert dataspaceName == 'my-dataspace' - assert schemaSetName == 'my-schemaset-name' - assert operation == Content.Operation.DELETE - assert data == null - } - } - - def 'Create CPS Data Event with URI Syntax Exception'() { - given: 'an anchor' - def anchor = new Anchor('my-anchorname', 'my-dataspace', 'my-schemaset-name') - and: 'a mocked data Node (collection)' - def mockDataNode = Mock(DataNode) - mockCpsDataService.getDataNodes(*_) >> [ mockDataNode ] - and: 'a URI syntax exception is thrown somewhere (using datanode as cannot manipulate hardcoded URIs' - def originalException = new URISyntaxException('input', 'reason', 0) - mockDataNode.getXpath() >> { throw originalException } - when: 'attempt to create data updated event' - objectUnderTest.createCpsDataUpdatedEvent(anchor, OffsetDateTime.now(), Operation.UPDATE) - then: 'the same exception is thrown up' - def thrownUp = thrown(URISyntaxException) - assert thrownUp == originalException - } - - def isExpectedDateTimeFormat(String observedTimestamp) { - try { - DateTimeFormatter.ofPattern(dateTimeFormat).parse(observedTimestamp) - } catch (DateTimeParseException) { - return false - } - return true - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaPublisherSpecBase.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/KafkaPublisherSpecBase.groovy deleted file mode 100644 index b60b38f054..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaPublisherSpecBase.groovy +++ /dev/null @@ -1,93 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification - -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.kafka.config.TopicBuilder -import org.springframework.kafka.core.ConsumerFactory -import org.springframework.kafka.core.KafkaAdmin -import org.springframework.kafka.core.KafkaTemplate -import org.springframework.kafka.listener.ConcurrentMessageListenerContainer -import org.springframework.kafka.listener.ContainerProperties -import org.springframework.kafka.listener.MessageListener -import org.springframework.kafka.test.utils.ContainerTestUtils -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.DynamicPropertyRegistry -import org.springframework.test.context.DynamicPropertySource -import spock.lang.Shared -import spock.lang.Specification - -@ContextConfiguration(classes = [KafkaAutoConfiguration, KafkaProducerListener, NotificationErrorHandler]) -@SpringBootTest -class KafkaPublisherSpecBase extends Specification { - - @Autowired - KafkaTemplate kafkaTemplate - - @Autowired - KafkaAdmin kafkaAdmin - - @Autowired - ConsumerFactory consumerFactory - - @Shared volatile topicCreated = false - @Shared consumedMessages = new ArrayList<>() - - def cpsEventTopic = 'cps-events' - - @DynamicPropertySource - static void registerKafkaProperties(DynamicPropertyRegistry registry) { - registry.add("spring.kafka.bootstrap-servers", KafkaTestContainerConfig::getBootstrapServers) - } - - def setup() { - // Kafka listener and topic should be created only once for a test-suite. - // We are also dependent on sprint context to achieve it, and can not execute it in setupSpec - if (!topicCreated) { - kafkaAdmin.createOrModifyTopics(TopicBuilder.name(cpsEventTopic).partitions(1).replicas(1).build()) - startListeningToTopic() - topicCreated = true - } - /* kafka message listener stores the messages to consumedMessages. - It is important to clear the list before each test case so that test cases can fetch the message from index '0'. - */ - consumedMessages.clear() - } - - def startListeningToTopic() { - ContainerProperties containerProperties = new ContainerProperties(cpsEventTopic) - containerProperties.setMessageListener([ - onMessage: { - record -> - consumedMessages.add(record.value()) - }] as MessageListener) - - ConcurrentMessageListenerContainer container = - new ConcurrentMessageListenerContainer<>( - consumerFactory, - containerProperties) - - container.start() - ContainerTestUtils.waitForAssignment(container, 1) - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy deleted file mode 100644 index b07b31a35b..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/KafkaTestContainerConfig.groovy +++ /dev/null @@ -1,49 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * ================================================================================ - * 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.notification - -import org.testcontainers.containers.KafkaContainer -import org.testcontainers.utility.DockerImageName - -class KafkaTestContainerConfig { - - private static KafkaContainer kafkaContainer - - static { - getKafkaContainer() - } - - // Not the best performance but it is good enough for test case - private static synchronized KafkaContainer getKafkaContainer() { - if (kafkaContainer == null) { - kafkaContainer = new KafkaContainer(DockerImageName.parse("registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1").asCompatibleSubstituteFor("confluentinc/cp-kafka")) - .withEnv("KAFKA_AUTO_CREATE_TOPICS_ENABLE", "false") - kafkaContainer.start() - Runtime.getRuntime().addShutdownHook(new Thread(kafkaContainer::stop)) - } - return kafkaContainer - } - - static String getBootstrapServers() { - getKafkaContainer() - return kafkaContainer.getBootstrapServers() - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy deleted file mode 100644 index 89e305aedb..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationErrorHandlerSpec.groovy +++ /dev/null @@ -1,60 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation - * ================================================================================ - * 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.notification - -import ch.qos.logback.classic.Logger -import ch.qos.logback.classic.spi.ILoggingEvent -import ch.qos.logback.core.read.ListAppender -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.slf4j.LoggerFactory - -import spock.lang.Specification - -class NotificationErrorHandlerSpec extends Specification{ - - NotificationErrorHandler objectUnderTest = new NotificationErrorHandler() - def logWatcher = Spy(ListAppender<ILoggingEvent>) - - @BeforeEach - void setup() { - ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).addAppender(logWatcher); - logWatcher.start(); - } - - @AfterEach - void teardown() { - ((Logger) LoggerFactory.getLogger(NotificationErrorHandler.class)).detachAndStopAllAppenders(); - } - - def 'Logging exception via notification error handler #scenario'() { - when: 'exception #scenario occurs' - objectUnderTest.onException(exception, 'some context') - then: 'log output results contains the correct error details' - def logMessage = logWatcher.list[0].getFormattedMessage() - assert logMessage.contains('Failed to process') - assert logMessage.contains("Error cause: ${exptectedCauseString}") - assert logMessage.contains("Error context: [some context]") - where: - scenario | exception || exptectedCauseString - 'with cause' | new Exception('message') || 'message' - 'without cause' | new Exception('message', new RuntimeException('cause')) || 'java.lang.RuntimeException: cause' - } -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationPublisherSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationPublisherSpec.groovy deleted file mode 100644 index 6cd9ae1b20..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationPublisherSpec.groovy +++ /dev/null @@ -1,91 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (C) 2021 Bell Canada. All rights reserved. - * Modifications Copyright (C) 2021-2022 Nordix Foundation - * ================================================================================ - * 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.notification - -import org.apache.kafka.clients.producer.ProducerRecord -import org.onap.cps.event.model.Content -import org.onap.cps.event.model.CpsDataUpdatedEvent -import org.spockframework.spring.SpringBean -import org.springframework.kafka.KafkaException -import org.springframework.kafka.core.KafkaTemplate -import spock.util.concurrent.PollingConditions - -class NotificationPublisherSpec extends KafkaPublisherSpecBase { - - @SpringBean - NotificationErrorHandler spyNotificationErrorHandler = Spy(new NotificationErrorHandler()) - - @SpringBean - KafkaProducerListener spyKafkaProducerListener = Spy(new KafkaProducerListener<>(spyNotificationErrorHandler)) - - KafkaTemplate spyKafkaTemplate - NotificationPublisher objectUnderTest - - def myAnchorName = 'my-anchor' - def myDataspaceName = 'my-dataspace' - - def cpsDataUpdatedEvent = new CpsDataUpdatedEvent() - .withContent(new Content() - .withDataspaceName(myDataspaceName) - .withAnchorName(myAnchorName)) - - def setup() { - spyKafkaTemplate = Spy(kafkaTemplate) - objectUnderTest = new NotificationPublisher(spyKafkaTemplate, cpsEventTopic); - } - - def 'Sending event to message bus with correct message Key.'() { - - when: 'event is sent to publisher' - objectUnderTest.sendNotification(cpsDataUpdatedEvent) - kafkaTemplate.flush() - - then: 'event is sent to correct topic with the expected messageKey' - interaction { - def messageKey = myDataspaceName + "," + myAnchorName - 1 * spyKafkaTemplate.send(cpsEventTopic, messageKey, cpsDataUpdatedEvent) - } - and: 'received a successful response' - 1 * spyKafkaProducerListener.onSuccess(_ as ProducerRecord, _) - and: 'kafka consumer returns expected message' - def conditions = new PollingConditions(timeout: 60, initialDelay: 0, factor: 1) - conditions.eventually { - assert cpsDataUpdatedEvent == consumedMessages.get(0) - } - } - - def 'Handling of async errors from message bus.'() { - given: 'topic does not exist' - objectUnderTest.topicName = 'non-existing-topic' - - when: 'message to sent to a non-existing topic' - objectUnderTest.sendNotification(cpsDataUpdatedEvent) - kafkaTemplate.flush() - - then: 'error is thrown' - thrown KafkaException - and: 'error handler is called with exception details' - 1 * spyKafkaProducerListener.onError(_ as ProducerRecord, _, _ as Exception) - 1 * spyNotificationErrorHandler.onException(_ as String, _ as Exception, - _ as ProducerRecord, _) - } - -} diff --git a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy deleted file mode 100644 index f07f89b391..0000000000 --- a/cps-service/src/test/groovy/org/onap/cps/notification/NotificationServiceSpec.groovy +++ /dev/null @@ -1,158 +0,0 @@ -/* - * ============LICENSE_START======================================================= - * Copyright (c) 2021-2022 Bell Canada. - * Modifications Copyright (C) 2022-2023 Nordix Foundation - * ================================================================================ - * 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.notification - -import org.onap.cps.api.CpsAdminService -import org.onap.cps.config.AsyncConfig -import org.onap.cps.event.model.CpsDataUpdatedEvent -import org.onap.cps.spi.model.Anchor -import org.spockframework.spring.SpringBean -import org.spockframework.spring.SpringSpy -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.context.properties.EnableConfigurationProperties -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.test.context.ContextConfiguration -import spock.lang.Shared -import spock.lang.Specification - -import java.time.OffsetDateTime -import java.util.concurrent.TimeUnit - -@SpringBootTest -@EnableConfigurationProperties -@ContextConfiguration(classes = [NotificationProperties, NotificationService, NotificationErrorHandler, AsyncConfig]) -class NotificationServiceSpec extends Specification { - - @SpringSpy - NotificationProperties spyNotificationProperties - @SpringBean - NotificationPublisher mockNotificationPublisher = Mock() - @SpringBean - CpsDataUpdatedEventFactory mockCpsDataUpdatedEventFactory = Mock() - @SpringSpy - NotificationErrorHandler spyNotificationErrorHandler - @SpringBean - CpsAdminService mockCpsAdminService = Mock() - - @Autowired - NotificationService objectUnderTest - - @Shared - def dataspaceName = 'my-dataspace-published' - @Shared - def anchorName = 'my-anchorname' - @Shared - def anchor = new Anchor('my-anchorname', 'my-dataspace-published', 'my-schemaset-name') - def myObservedTimestamp = OffsetDateTime.now() - - def setup() { - mockCpsAdminService.getAnchor(dataspaceName, anchorName) >> anchor - } - - def 'Skip sending notification when disabled.'() { - given: 'notification is disabled' - spyNotificationProperties.isEnabled() >> false - when: 'dataUpdatedEvent is received' - objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp) - then: 'the notification is not sent' - 0 * mockNotificationPublisher.sendNotification(_) - } - - def 'Send notification when enabled: #scenario.'() { - given: 'notification is enabled' - spyNotificationProperties.isEnabled() >> true - and: 'an anchor is in dataspace where #scenario' - def anchor = new Anchor('my-anchorname', dataspaceName, 'my-schemaset-name') - and: 'event factory can create event successfully' - def cpsDataUpdatedEvent = new CpsDataUpdatedEvent() - mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >> cpsDataUpdatedEvent - when: 'dataUpdatedEvent is received' - def future = objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp) - and: 'wait for async processing to complete' - future.get(10, TimeUnit.SECONDS) - then: 'async process completed successfully' - future.isDone() - and: 'notification is sent' - expectedSendNotificationCount * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent) - where: - scenario | dataspaceName || expectedSendNotificationCount - 'dataspace name does not match filter' | 'does-not-match-pattern' || 0 - 'dataspace name matches filter' | 'my-dataspace-published' || 1 - } - - def '#scenario are changed with xpath #xpath and operation #operation'() { - given: 'notification is enabled' - spyNotificationProperties.isEnabled() >> true - and: 'event factory creates event if operation is #operation' - def cpsDataUpdatedEvent = new CpsDataUpdatedEvent() - mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, expectedOperationInEvent) >> - cpsDataUpdatedEvent - when: 'dataUpdatedEvent is received for #xpath' - def future = objectUnderTest.processDataUpdatedEvent(anchor, xpath, operation, myObservedTimestamp) - and: 'wait for async processing to complete' - future.get(10, TimeUnit.SECONDS) - then: 'async process completed successfully' - future.isDone() - and: 'notification is sent' - 1 * mockNotificationPublisher.sendNotification(cpsDataUpdatedEvent) - where: - scenario | xpath | operation || expectedOperationInEvent - 'Same event is sent when root nodes' | '' | Operation.CREATE || Operation.CREATE - 'Same event is sent when root nodes' | '' | Operation.UPDATE || Operation.UPDATE - 'Same event is sent when root nodes' | '' | Operation.DELETE || Operation.DELETE - 'Same event is sent when root nodes' | '/' | Operation.CREATE || Operation.CREATE - 'Same event is sent when root nodes' | '/' | Operation.UPDATE || Operation.UPDATE - 'Same event is sent when root nodes' | '/' | Operation.DELETE || Operation.DELETE - 'Same event is sent when container nodes' | '/parent' | Operation.CREATE || Operation.CREATE - 'Same event is sent when container nodes' | '/parent' | Operation.UPDATE || Operation.UPDATE - 'Same event is sent when container nodes' | '/parent' | Operation.DELETE || Operation.DELETE - 'UPDATE event is sent when non root nodes' | '/parent/child' | Operation.CREATE || Operation.UPDATE - 'UPDATE event is sent when non root nodes' | '/parent/child' | Operation.UPDATE || Operation.UPDATE - 'UPDATE event is sent when non root nodes' | '/parent/child' | Operation.DELETE || Operation.UPDATE - } - - def 'Error handling in notification service.'() { - given: 'notification is enabled' - spyNotificationProperties.isEnabled() >> true - and: 'event factory can not create event successfully' - mockCpsDataUpdatedEventFactory.createCpsDataUpdatedEvent(anchor, myObservedTimestamp, Operation.CREATE) >> - { throw new Exception("Could not create event") } - when: 'event is sent for processing' - def future = objectUnderTest.processDataUpdatedEvent(anchor, '/', Operation.CREATE, myObservedTimestamp) - and: 'wait for async processing to complete' - future.get(10, TimeUnit.SECONDS) - then: 'async process completed successfully' - future.isDone() - and: 'error is handled and not thrown to caller' - notThrown Exception - 1 * spyNotificationErrorHandler.onException(_, _, _, '/', Operation.CREATE) - } - - def 'Disabled Notification services'() { - given: 'a notification service that is disabled' - spyNotificationProperties.enabled >> false - NotificationService notificationService = new NotificationService(spyNotificationProperties, mockNotificationPublisher, mockCpsDataUpdatedEventFactory, spyNotificationErrorHandler, mockCpsAdminService) - notificationService.init() - expect: 'it will not send notifications' - assert notificationService.shouldSendNotification('') == false - } -} diff --git a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy index ff6ab346f2..b975de6555 100644 --- a/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy +++ b/cps-service/src/test/groovy/org/onap/cps/utils/PrefixResolverSpec.groovy @@ -24,7 +24,7 @@ package org.onap.cps.utils import com.hazelcast.map.IMap import org.onap.cps.TestUtils -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.impl.YangTextSchemaSourceSetCache import org.onap.cps.cache.AnchorDataCacheEntry import org.onap.cps.spi.model.Anchor @@ -34,13 +34,13 @@ import spock.lang.Specification class PrefixResolverSpec extends Specification { - def mockCpsAdminService = Mock(CpsAdminService) + def mockCpsAnchorService = Mock(CpsAnchorService) def mockYangTextSchemaSourceSetCache = Mock(YangTextSchemaSourceSetCache) def mockAnchorDataCache = Mock(IMap<String, AnchorDataCacheEntry>) - def objectUnderTest = new PrefixResolver(mockCpsAdminService, mockYangTextSchemaSourceSetCache, mockAnchorDataCache) + def objectUnderTest = new PrefixResolver(mockCpsAnchorService, mockYangTextSchemaSourceSetCache, mockAnchorDataCache) def mockYangTextSchemaSourceSet = Mock(YangTextSchemaSourceSet) @@ -52,7 +52,7 @@ class PrefixResolverSpec extends Specification { given: 'an anchor for the test-tree model' def anchor = new Anchor(dataspaceName: 'testDataspace', name: 'testAnchor') and: 'the system can get this anchor' - mockCpsAdminService.getAnchor('testDataspace', 'testAnchor') >> anchor + mockCpsAnchorService.getAnchor('testDataspace', 'testAnchor') >> anchor and: 'the schema source cache contains the schema context for the test-tree module' mockYangTextSchemaSourceSet.getSchemaContext() >> schemaContext } diff --git a/docker-compose/README.md b/docker-compose/README.md index 8cc6e57d59..de1fbe296c 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -57,9 +57,7 @@ It starts both Postgres database and CPS services. 2. To send data-updated events to kafka, * uncomment the `zookeeper` and `kafka` services. * uncomment environment variables - * `notification.data-updated.enabled: 'true'` * `KAFKA_BOOTSTRAP_SERVER: kafka:9092` - * `NOTIFICATION_DATASPACE_FILTER_PATTERNS: '.*'` 2. Execute following command from `docker-compose` folder: Use one of the below version type that has been generated in the local system's docker image list after the build. diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index acfe984976..80f8782af0 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -101,8 +101,6 @@ services: DMI_USERNAME: ${DMI_USERNAME:-cpsuser} DMI_PASSWORD: ${DMI_PASSWORD:-cpsr0cks!} KAFKA_BOOTSTRAP_SERVER: kafka:29092 - notification.data-updated.enabled: 'true' - NOTIFICATION_DATASPACE_FILTER_PATTERNS: '.*' restart: unless-stopped profiles: - dmi-service diff --git a/docs/deployment.rst b/docs/deployment.rst index eb5804e5bf..76d2151c44 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -235,9 +235,6 @@ Any spring supported property can be configured by providing in ``config.additio | ssl.endpoint.identification.algorithm | | | | | * ``""``, empty string to disable | | +---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.additional. | If asynchronous messaging, user notifications, and updated event persistence should be enabled | ``true`` | -| notification.data-updated.enabled | | | -+---------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ | config.additional. | Core pool size in asynchronous execution of notification. | ``2`` | | notification.async.executor. | | | | core-pool-size | | | diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 63a877d811..1dc24bf3f2 100755 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -44,6 +44,7 @@ Features -------- - CPS-Temporal is no longer supported and any related documentation has been removed. - `CPS-1980 <https://jira.onap.org/browse/CPS-1980>`_ Exposing health and cluster metrics for hazelcast. + - `CPS-2005 <https://jira.onap.org/browse/CPS-2005>`_ Removing notification feature for cps updated events ( exclusively used by cps-temporal ) Version: 3.4.0 diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy index 40fe030184..6dec3dbf95 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -20,10 +20,11 @@ package org.onap.cps.integration.base +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.CpsQueryService -import org.onap.cps.api.impl.CpsAdminServiceImpl -import org.onap.cps.api.impl.CpsDataServiceImpl -import org.onap.cps.api.impl.CpsModuleServiceImpl import org.onap.cps.integration.DatabaseTestContainer import org.onap.cps.spi.config.CpsSessionFactory import org.onap.cps.spi.exceptions.DataspaceNotFoundException @@ -44,7 +45,7 @@ import spock.lang.Specification import java.time.OffsetDateTime -@SpringBootTest(classes = [TestConfig, CpsAdminServiceImpl, CpsValidatorImpl, SessionManager, CpsSessionFactory]) +@SpringBootTest(classes = [TestConfig, CpsValidatorImpl, SessionManager, CpsSessionFactory]) @Testcontainers @EnableAutoConfiguration @EnableJpaRepositories(basePackageClasses = [DataspaceRepository]) @@ -57,15 +58,19 @@ class CpsIntegrationSpecBase extends Specification { @Autowired @Lazy - CpsAdminServiceImpl cpsAdminService + CpsDataspaceService cpsDataspaceService @Autowired @Lazy - CpsDataServiceImpl cpsDataService + CpsAnchorService cpsAnchorService @Autowired @Lazy - CpsModuleServiceImpl cpsModuleService + CpsDataService cpsDataService + + @Autowired + @Lazy + CpsModuleService cpsModuleService @Autowired @Lazy @@ -83,7 +88,7 @@ class CpsIntegrationSpecBase extends Specification { def setup() { if (!initialized) { - cpsAdminService.createDataspace(GENERAL_TEST_DATASPACE) + cpsDataspaceService.createDataspace(GENERAL_TEST_DATASPACE) def bookstoreModelFileContent = readResourceDataFile('bookstore/bookstore.yang') cpsModuleService.createSchemaSet(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, [bookstore : bookstoreModelFileContent]) initialized = true; @@ -108,7 +113,7 @@ class CpsIntegrationSpecBase extends Specification { def dataspaceExists(dataspaceName) { try { - cpsAdminService.getDataspace(dataspaceName) + cpsDataspaceService.getDataspace(dataspaceName) } catch (DataspaceNotFoundException dataspaceNotFoundException) { return false } @@ -117,7 +122,7 @@ class CpsIntegrationSpecBase extends Specification { def addAnchorsWithData(numberOfAnchors, dataspaceName, schemaSetName, anchorNamePrefix, data) { (1..numberOfAnchors).each { - cpsAdminService.createAnchor(dataspaceName, schemaSetName, anchorNamePrefix + it) + cpsAnchorService.createAnchor(dataspaceName, schemaSetName, anchorNamePrefix + it) cpsDataService.saveData(dataspaceName, anchorNamePrefix + it, data.replace("Easons", "Easons-"+it.toString()), OffsetDateTime.now()) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy index 14612d6c13..b10194560a 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/FunctionalSpecBase.groovy @@ -50,9 +50,9 @@ class FunctionalSpecBase extends CpsIntegrationSpecBase { } def setupBookstoreInfraStructure() { - cpsAdminService.createDataspace(FUNCTIONAL_TEST_DATASPACE_1) - cpsAdminService.createDataspace(FUNCTIONAL_TEST_DATASPACE_2) - cpsAdminService.createDataspace(FUNCTIONAL_TEST_DATASPACE_3) + cpsDataspaceService.createDataspace(FUNCTIONAL_TEST_DATASPACE_1) + cpsDataspaceService.createDataspace(FUNCTIONAL_TEST_DATASPACE_2) + cpsDataspaceService.createDataspace(FUNCTIONAL_TEST_DATASPACE_3) def bookstoreYangModelAsString = readResourceDataFile('bookstore/bookstore.yang') cpsModuleService.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, [bookstore: bookstoreYangModelAsString]) cpsModuleService.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_2, BOOKSTORE_SCHEMA_SET, [bookstore: bookstoreYangModelAsString]) @@ -73,8 +73,8 @@ class FunctionalSpecBase extends CpsIntegrationSpecBase { def restoreBookstoreDataAnchor(anchorNumber) { def anchorName = 'bookstoreAnchor' + anchorNumber - cpsAdminService.deleteAnchor(FUNCTIONAL_TEST_DATASPACE_1, anchorName) - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, anchorName) + cpsAnchorService.deleteAnchor(FUNCTIONAL_TEST_DATASPACE_1, anchorName) + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, anchorName) cpsDataService.saveData(FUNCTIONAL_TEST_DATASPACE_1, anchorName, bookstoreJsonData.replace("Easons", "Easons-"+anchorNumber.toString()), OffsetDateTime.now()) } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy index f4cc8b733f..e1ccdaa6ee 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/TestConfig.groovy @@ -21,7 +21,6 @@ package org.onap.cps.integration.base import com.fasterxml.jackson.databind.ObjectMapper -import org.onap.cps.notification.NotificationService import org.onap.cps.spi.CpsDataPersistenceService import org.onap.cps.spi.CpsModulePersistenceService import org.onap.cps.spi.impl.CpsAdminPersistenceServiceImpl @@ -99,11 +98,6 @@ class TestConfig extends Specification{ } @Bean - NotificationService notificationService() { - return Stub(NotificationService) - } - - @Bean TimedYangParser timedYangParser() { return new TimedYangParser() } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAnchorServiceIntegrationSpec.groovy index f8eca61990..99dab84260 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAdminServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAnchorServiceIntegrationSpec.groovy @@ -20,76 +20,19 @@ package org.onap.cps.integration.functional -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.AnchorNotFoundException -import org.onap.cps.spi.exceptions.DataspaceInUseException -import org.onap.cps.spi.exceptions.DataspaceNotFoundException -import java.time.OffsetDateTime - -class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase { - - CpsAdminService objectUnderTest - def setup() { objectUnderTest = cpsAdminService } - - def 'Dataspace CRUD operations.'() { - when: 'a dataspace is created' - objectUnderTest.createDataspace('newDataspace') - then: 'the dataspace can be read' - assert objectUnderTest.getDataspace('newDataspace').name == 'newDataspace' - and: 'it can be deleted' - objectUnderTest.deleteDataspace('newDataspace') - then: 'the dataspace no longer exists i.e. an exception is thrown if an attempt is made to retrieve it' - def thrown = null - try { - objectUnderTest.getDataspace('newDataspace') - } catch(Exception exception) { - thrown = exception - } - assert thrown instanceof DataspaceNotFoundException - } +import java.time.OffsetDateTime - def 'Delete dataspace with error; #scenario.'() { - setup: 'add some anchors if needed' - numberOfAnchors.times { - objectUnderTest.createAnchor(GENERAL_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'anchor' + it) - } - when: 'attempt to delete dataspace' - objectUnderTest.deleteDataspace(dataspaceName) - then: 'the correct exception is thrown with the relevant details' - def thrownException = thrown(expectedException) - thrownException.details.contains(expectedMessageDetails) - cleanup: - numberOfAnchors.times { - objectUnderTest.deleteAnchor(GENERAL_TEST_DATASPACE, 'anchor' + it) - } - where: 'the following data is used' - scenario | dataspaceName | numberOfAnchors || expectedException | expectedMessageDetails - 'dataspace name does not exist' | 'unknown' | 0 || DataspaceNotFoundException | 'unknown does not exist' - 'dataspace contains schemasets' | GENERAL_TEST_DATASPACE | 0 || DataspaceInUseException | 'contains 1 schemaset(s)' - 'dataspace contains anchors' | GENERAL_TEST_DATASPACE | 2 || DataspaceInUseException | 'contains 2 anchor(s)' - } +class CpsAnchorServiceIntegrationSpec extends CpsIntegrationSpecBase { - def 'Retrieve all dataspaces (depends on total test suite).'() { - given: 'two addtional dataspaces are created' - objectUnderTest.createDataspace('dataspace1') - objectUnderTest.createDataspace('dataspace2') - when: 'all datespaces are retreived' - def result = objectUnderTest.getAllDataspaces() - then: 'there are at least 3 dataspaces (2 new ones plus the general test dataspace)' - result.size() >= 3 - assert result.name.containsAll([GENERAL_TEST_DATASPACE, 'dataspace1', 'dataspace2']) - } + CpsAnchorService objectUnderTest - def 'Duplicate dataspaces.'() { - when: 'attempting to create a dataspace with the same name as an existing one' - objectUnderTest.createDataspace(GENERAL_TEST_DATASPACE) - then: 'an exception is thrown indicating the dataspace already exists' - thrown(AlreadyDefinedException) - } + def setup() { objectUnderTest = cpsAnchorService } def 'Anchor CRUD operations.'() { when: 'an anchor is created' @@ -148,9 +91,9 @@ class CpsAdminServiceIntegrationSpec extends CpsIntegrationSpecBase { then: 'an empty result is returned (no error)' assert result == [] where: - scenario | dataspaceName - 'non existing database' | 'nonExistingDataspace' - 'just unknown module(s)' | GENERAL_TEST_DATASPACE + scenario | dataspaceName + 'non existing database' | 'nonExistingDataspace' + 'just unknown module(s)' | GENERAL_TEST_DATASPACE } def 'Update anchor schema set.'() { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy index 017ede7de2..b107a87e88 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy @@ -34,8 +34,6 @@ import org.onap.cps.spi.exceptions.DataValidationException import org.onap.cps.spi.exceptions.DataspaceNotFoundException import org.onap.cps.spi.model.DeltaReport -import java.time.OffsetDateTime - import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS @@ -174,7 +172,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def 'Attempt to create a top level data node using root.'() { given: 'a new anchor' - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, 'newAnchor1'); + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, 'newAnchor1'); when: 'attempt to save new top level datanode' def json = '{"bookstore": {"bookstore-name": "New Store"} }' objectUnderTest.saveData(FUNCTIONAL_TEST_DATASPACE_1, 'newAnchor1' , '/', json, now) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataspaceServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataspaceServiceIntegrationSpec.groovy new file mode 100644 index 0000000000..739e802244 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataspaceServiceIntegrationSpec.groovy @@ -0,0 +1,107 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * 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.integration.functional + +import org.onap.cps.api.CpsDataspaceService +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.spi.exceptions.AlreadyDefinedException +import org.onap.cps.spi.exceptions.DataspaceInUseException +import org.onap.cps.spi.exceptions.DataspaceNotFoundException + +class CpsDataspaceServiceIntegrationSpec extends CpsIntegrationSpecBase { + + CpsDataspaceService objectUnderTest + + def setup() { objectUnderTest = cpsDataspaceService } + + def 'Dataspace CRUD operations.'() { + when: 'a dataspace is created' + objectUnderTest.createDataspace('newDataspace') + then: 'the dataspace can be read' + assert objectUnderTest.getDataspace('newDataspace').name == 'newDataspace' + and: 'it can be deleted' + objectUnderTest.deleteDataspace('newDataspace') + then: 'the dataspace no longer exists i.e. an exception is thrown if an attempt is made to retrieve it' + def thrown = null + try { + objectUnderTest.getDataspace('newDataspace') + } catch(Exception exception) { + thrown = exception + } + assert thrown instanceof DataspaceNotFoundException + } + + def 'Attempt to delete a non-existing dataspace'() { + when: 'attempt to delete a non-existing dataspace' + objectUnderTest.deleteDataspace('non-existing-name') + then: 'a not found exception is thrown with the relevant dataspace name' + def thrownException = thrown(DataspaceNotFoundException) + assert thrownException.details.contains('non-existing-name does not exist') + } + + def 'Attempt Delete dataspace with a schema set and anchor'() { + setup: 'a dataspace with a schema set and anchor' + objectUnderTest.createDataspace('targetDataspace') + cpsModuleService.createSchemaSet('targetDataspace','someSchemaSet',[:]) + cpsAnchorService.createAnchor('targetDataspace', 'someSchemaSet', 'some_anchor') + when: 'attempt to delete dataspace' + objectUnderTest.deleteDataspace('targetDataspace') + then: 'an in-use exception is thrown mentioning anchors' + def thrownException = thrown(DataspaceInUseException) + assert thrownException.details.contains('contains 1 anchor(s)') + cleanup: + cpsModuleService.deleteSchemaSetsWithCascade('targetDataspace',['someSchemaSet']) + objectUnderTest.deleteDataspace('targetDataspace') + } + + def 'Attempt to delete dataspace with just a schema set'() { + setup: 'a dataspace with a schema set' + objectUnderTest.createDataspace('targetDataspace') + cpsModuleService.createSchemaSet('targetDataspace','someSchemaSet',[:]) + when: 'attempt to delete dataspace' + objectUnderTest.deleteDataspace('targetDataspace') + then: 'an in-use exception is thrown mentioning schemasets' + def thrownException = thrown(DataspaceInUseException) + assert thrownException.details.contains('contains 1 schemaset(s)') + cleanup: + cpsModuleService.deleteSchemaSetsWithCascade('targetDataspace',['someSchemaSet']) + objectUnderTest.deleteDataspace('targetDataspace') + } + + def 'Retrieve all dataspaces (depends on total test suite).'() { + given: 'two addtional dataspaces are created' + objectUnderTest.createDataspace('dataspace1') + objectUnderTest.createDataspace('dataspace2') + when: 'all datespaces are retreived' + def result = objectUnderTest.getAllDataspaces() + then: 'there are at least 3 dataspaces (2 new ones plus the general test dataspace)' + result.size() >= 3 + assert result.name.containsAll([GENERAL_TEST_DATASPACE, 'dataspace1', 'dataspace2']) + } + + def 'Duplicate dataspaces.'() { + when: 'attempting to create a dataspace with the same name as an existing one' + objectUnderTest.createDataspace(GENERAL_TEST_DATASPACE) + then: 'an exception is thrown indicating the dataspace already exists' + thrown(AlreadyDefinedException) + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy index 2e1b082c00..cf0e0b5944 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy @@ -20,6 +20,7 @@ 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 @@ -33,7 +34,7 @@ import org.onap.cps.spi.model.ModuleReference class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { - CpsModuleServiceImpl objectUnderTest + CpsModuleService objectUnderTest private static def originalNumberOfModuleReferences = 1 private static def existingModuleReference = new ModuleReference('stores','2020-09-15') @@ -97,7 +98,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { def schemaSetName = "NewSchemaWith${numberOfNewModules}Modules" objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, schemaSetName, newYangResourcesNameToContentMap, moduleReferences) and: 'associated with a new anchor' - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, schemaSetName, 'newAnchor') + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, schemaSetName, 'newAnchor') then: 'the new anchor has the correct number of modules' def yangResourceModuleReferences = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'newAnchor') assert expectedNumberOfModulesForAnchor == yangResourceModuleReferences.size() @@ -204,7 +205,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { objectUnderTest.createSchemaSet(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', newYangResourcesNameToContentMap) and: 'optionally create anchor for the schema set' if (associateWithAnchor) { - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', 'newAnchor') + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'newSchemaSet', 'newAnchor') } when: 'attempt to delete the schema set' try { @@ -270,7 +271,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { given: 'an anchor and schema set with 2 modules (to be upgraded)' populateNewYangResourcesNameToContentMapAndAllModuleReferences('original', 2) objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', newYangResourcesNameToContentMap, []) - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', 'targetAnchor') + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', 'targetAnchor') def yangResourceModuleReferencesBeforeUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor') assert yangResourceModuleReferencesBeforeUpgrade.size() == 2 assert yangResourceModuleReferencesBeforeUpgrade.containsAll([new ModuleReference('original_0','2000-01-01'),new ModuleReference('original_1','2001-01-01')]) @@ -291,18 +292,18 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { cleanup: objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['targetSchema']) } - + def 'Upgrade existing schema set from another anchor (used in NCMP for matching module set tag)'() { given: 'an anchor and schema set with 1 module (target)' populateNewYangResourcesNameToContentMapAndAllModuleReferences('target', 1) objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', newYangResourcesNameToContentMap, []) - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', 'targetAnchor') + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'targetSchema', 'targetAnchor') def moduleReferencesBeforeUpgrade = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'targetAnchor') assert moduleReferencesBeforeUpgrade.size() == 1 and: 'another anchor and schema set with 2 other modules (source for upgrade)' populateNewYangResourcesNameToContentMapAndAllModuleReferences('source', 2) objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, 'sourceSchema', newYangResourcesNameToContentMap, []) - cpsAdminService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'sourceSchema', 'sourceAnchor') + cpsAnchorService.createAnchor(FUNCTIONAL_TEST_DATASPACE_1, 'sourceSchema', 'sourceAnchor') assert objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'sourceAnchor').size() == 2 when: 'the target schema is upgraded using the module references from the source anchor' def moduleReferencesFromSourceAnchor = objectUnderTest.getYangResourcesModuleReferences(FUNCTIONAL_TEST_DATASPACE_1, 'sourceAnchor') diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy index d8012ec6d4..816aeff71b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/CpsPerfTestBase.groovy @@ -43,7 +43,7 @@ class CpsPerfTestBase extends PerfTestBase { } def setupPerformanceInfraStructure() { - cpsAdminService.createDataspace(CPS_PERFORMANCE_TEST_DATASPACE) + cpsDataspaceService.createDataspace(CPS_PERFORMANCE_TEST_DATASPACE) def modelAsString = readResourceDataFile('bookstore/bookstore.yang') cpsModuleService.createSchemaSet(CPS_PERFORMANCE_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, [bookstore: modelAsString]) } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy index bef42248f9..0557d5bb7e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/base/NcmpPerfTestBase.groovy @@ -59,20 +59,20 @@ class NcmpPerfTestBase extends PerfTestBase { } def setupPerformanceInfraStructure() { - cpsAdminService.createDataspace(NCMP_PERFORMANCE_TEST_DATASPACE) + cpsDataspaceService.createDataspace(NCMP_PERFORMANCE_TEST_DATASPACE) createRegistrySchemaSet() createCmDataSubscriptionsSchemaSet() addCmSubscriptionData() } def createInitialData() { - cpsAdminService.createAnchor(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, REGISTRY_ANCHOR) + cpsDataspaceService.createAnchor(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, REGISTRY_ANCHOR) def data = readResourceDataFile('ncmp-registry/1000-cmhandles.json') cpsDataService.saveData(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, data, OffsetDateTime.now()) } def createRegistrySchemaSet() { - def modelAsString = readResourceDataFile('ncmp-registry/dmi-registry@2022-05-10.yang') + def modelAsString = readResourceDataFile('ncmp-registry/dmi-registry@2023-11-27.yang') cpsModuleService.createSchemaSet(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, [registry: modelAsString]) } @@ -82,7 +82,7 @@ class NcmpPerfTestBase extends PerfTestBase { } def addCmSubscriptionData() { - cpsAdminService.createAnchor(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_SCHEMA_SET, CM_DATA_SUBSCRIPTIONS_ANCHOR) + cpsDataspaceService.createAnchor(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_SCHEMA_SET, CM_DATA_SUBSCRIPTIONS_ANCHOR) cpsDataService.saveData(NCMP_PERFORMANCE_TEST_DATASPACE, CM_DATA_SUBSCRIPTIONS_ANCHOR, datastore1cmHandlePlaceHolder, now) def subscribers = createLeafList('subscribers',numberOfCmDataSubscribers, subscriberIdPrefix) def filters = '"filters":' + createJsonArray('filter',numberOfFiltersPerCmHandle,'xpath',xpathPrefix,subscribers) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy index ce66cb08a2..4d4d6125c6 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataServiceLimitsPerfTest.groovy @@ -35,7 +35,7 @@ class CpsDataServiceLimitsPerfTest extends CpsPerfTestBase { def 'Create 33,000 books (note further tests depend on this running first).'() { given: 'an anchor containing a bookstore with one category' - cpsAdminService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'limitsAnchor') + cpsAnchorService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'limitsAnchor') def parentNodeData = '{"bookstore": { "categories": [{ "code": 1, "name": "Test", "books" : [] }] }}' cpsDataService.saveData(CPS_PERFORMANCE_TEST_DATASPACE, 'limitsAnchor', parentNodeData, OffsetDateTime.now()) when: '33,000 books are added' @@ -84,7 +84,7 @@ class CpsDataServiceLimitsPerfTest extends CpsPerfTestBase { when: resourceMeter.start() cpsDataService.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'limitsAnchor', OffsetDateTime.now()) - cpsAdminService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'limitsAnchor') + cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'limitsAnchor') resourceMeter.stop() def durationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'test data is deleted in 1 second' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimitsPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataspaceServiceLimitsPerfTest.groovy index 9ea7a7b53a..e1235272fe 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsAdminServiceLimitsPerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/CpsDataspaceServiceLimitsPerfTest.groovy @@ -20,14 +20,14 @@ package org.onap.cps.integration.performance.cps -import org.onap.cps.api.CpsAdminService +import org.onap.cps.api.CpsAnchorService import org.onap.cps.integration.performance.base.CpsPerfTestBase -class CpsAdminServiceLimitsPerfTest extends CpsPerfTestBase { +class CpsDataspaceServiceLimitsPerfTest extends CpsPerfTestBase { - CpsAdminService objectUnderTest + CpsAnchorService objectUnderTest - def setup() { objectUnderTest = cpsAdminService } + def setup() { objectUnderTest = cpsAnchorService } def 'Get anchors from multiple schema set names limit exceeded: 32,766 (~ 2^15) schema set names.'() { given: 'more than 32,766 schema set names' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy index 818c300a56..e7bfabefb5 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/DeletePerfTest.groovy @@ -182,7 +182,7 @@ class DeletePerfTest extends CpsPerfTestBase { def anchorNames = (1..10).collect {'delete' + it} when: 'data nodes are deleted' resourceMeter.start() - cpsAdminService.deleteAnchors(CPS_PERFORMANCE_TEST_DATASPACE, anchorNames) + cpsAnchorService.deleteAnchors(CPS_PERFORMANCE_TEST_DATASPACE, anchorNames) resourceMeter.stop() def deleteDurationInSeconds = resourceMeter.getTotalTimeInSeconds() then: 'delete duration is within expected time and memory used is within limit' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy index 0c7107a56d..2d38a0dfb6 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy @@ -27,7 +27,7 @@ class WritePerfTest extends CpsPerfTestBase { def 'Writing openroadm data has linear time.'() { given: 'an empty anchor exists for openroadm' - cpsAdminService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, LARGE_SCHEMA_SET, 'writeAnchor') + cpsAnchorService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, LARGE_SCHEMA_SET, 'writeAnchor') and: 'a list of device nodes to add' def jsonData = generateOpenRoadData(totalNodes) when: 'device nodes are added' @@ -39,7 +39,7 @@ class WritePerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Writing ${totalNodes} devices", expectedDuration, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) cleanup: cpsDataService.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor', OffsetDateTime.now()) - cpsAdminService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor') + cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor') where: totalNodes || expectedDuration | memoryLimit 50 || 4 | 100 @@ -50,7 +50,7 @@ class WritePerfTest extends CpsPerfTestBase { def 'Writing bookstore data has exponential time.'() { given: 'an anchor containing a bookstore with a single category' - cpsAdminService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'writeAnchor') + cpsAnchorService.createAnchor(CPS_PERFORMANCE_TEST_DATASPACE, BOOKSTORE_SCHEMA_SET, 'writeAnchor') def parentNodeData = '{"bookstore": { "categories": [{ "code": 1, "name": "Test", "books" : [] }] }}' cpsDataService.saveData(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor', parentNodeData, OffsetDateTime.now()) and: 'a list of books to add' @@ -64,7 +64,7 @@ class WritePerfTest extends CpsPerfTestBase { recordAndAssertResourceUsage("Writing ${totalBooks} books", expectedDuration, durationInSeconds, memoryLimit, resourceMeter.getTotalMemoryUsageInMB()) cleanup: cpsDataService.deleteDataNodes(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor', OffsetDateTime.now()) - cpsAdminService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor') + cpsAnchorService.deleteAnchor(CPS_PERFORMANCE_TEST_DATASPACE, 'writeAnchor') where: totalBooks || expectedDuration | memoryLimit 800 || 1 | 50 diff --git a/cps-ncmp-service/src/main/resources/models/dmi-registry@2023-08-23.yang b/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2023-11-27.yang index bb7604d91a..e3152cb4a1 100644 --- a/cps-ncmp-service/src/main/resources/models/dmi-registry@2023-08-23.yang +++ b/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2023-11-27.yang @@ -8,6 +8,11 @@ module dmi-registry { contact "toine.siebelink@est.tech"; + revision "2023-11-27" { + description + "Added alternateId"; + } + revision "2023-08-23" { description "Added ModuleSetTag"; @@ -83,6 +88,9 @@ module dmi-registry { leaf module-set-tag { type string; } + leaf alternate-id { + type string; + } list additional-properties { key "name"; @@ -129,3 +137,4 @@ module dmi-registry { } } } + |