diff options
74 files changed, 782 insertions, 283 deletions
diff --git a/checkstyle/pom.xml b/checkstyle/pom.xml index 0518655c18..893af40162 100644 --- a/checkstyle/pom.xml +++ b/checkstyle/pom.xml @@ -26,7 +26,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>checkstyle</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <profiles> <profile> diff --git a/cps-application/pom.xml b/cps-application/pom.xml index e8d32eac0e..c37447306c 100644 --- a/cps-application/pom.xml +++ b/cps-application/pom.xml @@ -28,7 +28,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index cf9c4fb009..3789e92b08 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -228,8 +228,6 @@ ncmp: timers: advised-modules-sync: sleep-time-ms: 5000 - locked-modules-sync: - sleep-time-ms: 15000 cm-handle-data-sync: sleep-time-ms: 30000 subscription-forwarding: diff --git a/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java new file mode 100644 index 0000000000..1d39060024 --- /dev/null +++ b/cps-application/src/test/java/org/onap/cps/architecture/ArchitectureTestBase.java @@ -0,0 +1,47 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.architecture; + +import org.apache.commons.lang3.ArrayUtils; + +public class ArchitectureTestBase { + + private static final String[] ACCEPTED_3PP_PACKAGES = { "com.fasterxml..", + "com.google..", + "com.hazelcast..", + "edu..", + "io.cloudevents..", + "io.micrometer..", + "io.netty..", + "io.swagger..", + "jakarta..", + "java..", + "lombok..", + "org.apache..", + "org.mapstruct..", + "org.slf4j..", + "org.springframework..", + "reactor.." + }; + + static String[] commonAndListedPackages(final String... packageIdentifiers) { + return ArrayUtils.addAll(ACCEPTED_3PP_PACKAGES, packageIdentifiers); + } +} diff --git a/cps-application/src/test/java/org/onap/cps/architecture/CpsArchitectureTest.java b/cps-application/src/test/java/org/onap/cps/architecture/CpsArchitectureTest.java new file mode 100644 index 0000000000..7e96447252 --- /dev/null +++ b/cps-application/src/test/java/org/onap/cps/architecture/CpsArchitectureTest.java @@ -0,0 +1,69 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; + +import com.tngtech.archunit.core.importer.ImportOption; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +@AnalyzeClasses(packages = "org.onap.cps", importOptions = {ImportOption.DoNotIncludeTests.class}) +public class CpsArchitectureTest extends ArchitectureTestBase { + + @ArchTest + static final ArchRule cpsRestControllerShouldOnlyDependOnCpsService = + classes().that().resideInAPackage("org.onap.cps.rest..").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.rest..", + "org.onap.cps.api..", + "org.onap.cps.utils..", + // Breaks arch rules + "org.onap.cps.spi..")); + + @ArchTest + static final ArchRule cpsServiceApiShouldNotDependOnAnything = + classes().that().resideInAPackage("org.onap.cps.api.").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages()).allowEmptyShould(true); + + @ArchTest + static final ArchRule cpsServiceImplShouldDependOnServiceAndEventsAndPathParserPackages = + // I think impl package should be moved from the api package. + // So in a way this whole rule is breaking our architecture goals + classes().that().resideInAPackage("org.onap.cps.api.impl..").should().onlyDependOnClassesThat() + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.api..", + "org.onap.cps.api.impl..", + "org.onap.cps.events..", + "org.onap.cps.impl.utils..", + "org.onap.cps.spi..", + "org.onap.cps.utils..", + "org.onap.cps.cpspath.parser..", + "org.onap.cps.yang..")); + + @ArchTest + static final ArchRule cpsReferenceImplShouldHaveNoDependants = + classes().that().resideInAPackage("org.onap.cps.ri..").should().onlyHaveDependentClassesThat() + .resideInAnyPackage("org.onap.cps.ri.."); + + @ArchTest + static final ArchRule referenceImplShouldOnlyHaveDependantsInReferenceImpl = + classes().that().resideInAPackage("org.onap.cps.ri.repository..").should().onlyHaveDependentClassesThat() + .resideInAnyPackage("org.onap.cps.ri.."); +} diff --git a/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java b/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java index 4c0b82fec8..5d8c534e35 100644 --- a/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java +++ b/cps-application/src/test/java/org/onap/cps/architecture/NcmpArchitectureTest.java @@ -25,31 +25,9 @@ import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; import com.tngtech.archunit.junit.ArchTest; import com.tngtech.archunit.lang.ArchRule; -import org.apache.commons.lang3.ArrayUtils; -/** - * These test verify the correct use of dependencies in all CPS modules. - */ @AnalyzeClasses(packages = "org.onap.cps", importOptions = {ImportOption.DoNotIncludeTests.class}) -public class NcmpArchitectureTest { - - private static final String[] ACCEPTED_3PP_PACKAGES = { "com.fasterxml..", - "com.google..", - "com.hazelcast..", - "edu..", - "io.cloudevents..", - "io.micrometer..", - "io.netty..", - "io.swagger..", - "jakarta..", - "java..", - "lombok..", - "org.apache..", - "org.mapstruct..", - "org.slf4j..", - "org.springframework..", - "reactor.." - }; +public class NcmpArchitectureTest extends ArchitectureTestBase { @ArchTest static final ArchRule nothingDependsOnCpsNcmpRest = @@ -61,38 +39,45 @@ public class NcmpArchitectureTest { classes().that().resideInAPackage("org.onap.cps.ncmp.rest..") .should() .onlyDependOnClassesThat() - .resideInAnyPackage(commonAndListedPackages("org.onap.cps.ncmp.rest..", "org.onap.cps.ncmp.api..", - // Below packages are breaking the agreed dependencies - // and need to be removed from this rule. - // This will be handled in a separate user story - "org.onap.cps.spi..", "org.onap.cps.utils..", "org.onap.cps.ncmp.impl..")); + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.ncmp.api..", + "org.onap.cps.ncmp.rest..", + // Below packages are breaking the agreed dependencies + // and need to be removed from this rule. + // This will be handled in a separate user story + "org.onap.cps.spi..", + "org.onap.cps.utils..", + "org.onap.cps.ncmp.impl..")); @ArchTest static final ArchRule ncmpServiceApiShouldOnlyDependOnThirdPartyPackages = classes().that().resideInAPackage("org.onap.cps.ncmp.api..").should().onlyDependOnClassesThat() - .resideInAnyPackage(commonAndListedPackages( - // Below packages are breaking the agreed dependencies - // and need to be removed from this rule. - // This will be handled in a separate user story - "org.onap.cps.spi..", "org.onap.cps.ncmp.api..", "org.onap.cps.ncmp.impl..", - "org.onap.cps.ncmp.config", "org.onap.cps.utils..")); + .resideInAnyPackage(commonAndListedPackages(// Below packages are breaking the agreed dependencies + // and need to be removed from this rule. + // This will be handled in a separate user story + "org.onap.cps.ncmp.api..", + "org.onap.cps.ncmp.impl..", + "org.onap.cps.ncmp.config", + "org.onap.cps.spi..", + "org.onap.cps.utils..")); @ArchTest static final ArchRule ncmpServiceImplShouldOnlyDependOnCpsServiceAndNcmpEvents = classes().that().resideInAPackage("org.onap.cps.ncmp.impl..").should().onlyDependOnClassesThat() - .resideInAnyPackage(commonAndListedPackages( - "org.onap.cps.ncmp.api..", "org.onap.cps.ncmp.impl..", - "org.onap.cps.ncmp.event..", "org.onap.cps.ncmp.events..", "org.onap.cps.ncmp.utils..", - "org.onap.cps.ncmp.config..", "org.onap.cps.api..", "org.onap.cps.ncmp.exceptions..", - // Below packages are breaking the agreed dependencies - // and need to be removed from this rule. - // This will be handled in a separate user story - "org.onap.cps.spi..", "org.onap.cps.events..", "org.onap.cps.cpspath..", - "org.onap.cps.impl..", "org.onap.cps.utils..")); - - static String[] commonAndListedPackages(final String... packageIdentifiers) { - return ArrayUtils.addAll(ACCEPTED_3PP_PACKAGES, packageIdentifiers); - } - + .resideInAnyPackage(commonAndListedPackages("org.onap.cps.api..", + "org.onap.cps.ncmp.api..", + "org.onap.cps.ncmp.impl..", + "org.onap.cps.ncmp.event..", + "org.onap.cps.ncmp.events..", + "org.onap.cps.ncmp.utils..", + "org.onap.cps.ncmp.config..", + "org.onap.cps.ncmp.exceptions..", + // Below packages are breaking the agreed dependencies + // and need to be removed from this rule. + // This will be handled in a separate user story + "org.onap.cps.cpspath..", + "org.onap.cps.events..", + "org.onap.cps.impl..", + "org.onap.cps.spi..", + "org.onap.cps.utils..")); } diff --git a/cps-bom/pom.xml b/cps-bom/pom.xml index 93f4b818ff..6521830f2a 100644 --- a/cps-bom/pom.xml +++ b/cps-bom/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-bom</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <packaging>pom</packaging> <description>This artifact contains dependencyManagement declarations of all published CPS components.</description> diff --git a/cps-dependencies/pom.xml b/cps-dependencies/pom.xml index 58e87a8031..a95e5f42b0 100644 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -27,7 +27,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>cps-dependencies</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <packaging>pom</packaging> <name>${project.groupId}:${project.artifactId}</name> @@ -131,12 +131,7 @@ <dependency> <groupId>com.github.spotbugs</groupId> <artifactId>spotbugs-annotations</artifactId> - <version>3.1.3</version> - </dependency> - <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>annotations</artifactId> - <version>3.0.1</version> + <version>4.8.6</version> </dependency> <dependency> <groupId>com.google.code.gson</groupId> diff --git a/cps-events/pom.xml b/cps-events/pom.xml index 16a8817080..4c951cb362 100644 --- a/cps-events/pom.xml +++ b/cps-events/pom.xml @@ -24,7 +24,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-events/pom.xml b/cps-ncmp-events/pom.xml index bd81f74327..0ca3a51dc1 100644 --- a/cps-ncmp-events/pom.xml +++ b/cps-ncmp-events/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml index aaf884f4dd..67e1ed29ed 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-app/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-ncmp-rest-stub</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> </parent> <artifactId>cps-ncmp-rest-stub-app</artifactId> diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml index e778e027b2..1fcea8f567 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml @@ -21,7 +21,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-ncmp-rest-stub</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> </parent> <artifactId>cps-ncmp-rest-stub-service</artifactId> diff --git a/cps-ncmp-rest-stub/pom.xml b/cps-ncmp-rest-stub/pom.xml index be361a4504..aecdde1528 100644 --- a/cps-ncmp-rest-stub/pom.xml +++ b/cps-ncmp-rest-stub/pom.xml @@ -22,7 +22,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-ncmp-rest/docs/openapi/openapi-inventory.yml b/cps-ncmp-rest/docs/openapi/openapi-inventory.yml index 8c0ad41705..d374dcc9db 100755 --- a/cps-ncmp-rest/docs/openapi/openapi-inventory.yml +++ b/cps-ncmp-rest/docs/openapi/openapi-inventory.yml @@ -1,6 +1,6 @@ # ============LICENSE_START======================================================= # Copyright (C) 2021 Bell Canada -# Modifications Copyright (C) 2022-2023 Nordix Foundation +# Modifications Copyright (C) 2022-2024 Nordix Foundation # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ openapi: 3.0.3 info: title: NCMP Inventory API description: NCMP Inventory API - version: "3.5.2" + version: "3.5.4" servers: - url: /ncmpInventory components: diff --git a/cps-ncmp-rest/docs/openapi/openapi.yml b/cps-ncmp-rest/docs/openapi/openapi.yml index 78fb141820..dd2706a795 100755 --- a/cps-ncmp-rest/docs/openapi/openapi.yml +++ b/cps-ncmp-rest/docs/openapi/openapi.yml @@ -22,7 +22,7 @@ openapi: 3.0.3 info: title: NCMP to CPS Proxy API description: NCMP to CPS Proxy API - version: "3.5.2" + version: "3.5.4" servers: - url: /ncmp components: diff --git a/cps-ncmp-rest/pom.xml b/cps-ncmp-rest/pom.xml index d46767e72c..c78fe55a59 100644 --- a/cps-ncmp-rest/pom.xml +++ b/cps-ncmp-rest/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -231,6 +231,25 @@ </resources> </configuration> </execution> + <execution> + <id>copy-to-docs-folder</id> + <phase>compile</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/../docs/api/swagger/ncmp</outputDirectory> + <overwrite>true</overwrite> + <resources> + <resource> + <directory>${project.basedir}/target/generated-sources/openapi/</directory> + <includes> + <include>openapi*.yaml</include> + </includes> + </resource> + </resources> + </configuration> + </execution> </executions> </plugin> </plugins> diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java index f47c05ca4e..f76376991a 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryController.java @@ -94,7 +94,7 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor public ResponseEntity updateDmiPluginRegistration( final @Valid RestDmiPluginRegistration restDmiPluginRegistration) { final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule( + networkCmProxyInventoryFacade.updateDmiRegistration( ncmpRestInputMapper.toDmiPluginRegistration(restDmiPluginRegistration)); final DmiPluginRegistrationErrorResponse failedRegistrationErrorResponse = getFailureRegistrationResponse(dmiPluginRegistrationResponse); diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy index 85b2dd1a4d..74e6759f72 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyInventoryControllerSpec.groovy @@ -84,7 +84,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { .content(jsonData) ).andReturn().response then: 'the converted object is forwarded to the registration service' - 1 * mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse() + 1 * mockNetworkCmProxyInventoryFacade.updateDmiRegistration(mockDmiPluginRegistration) >> new DmiPluginRegistrationResponse() and: 'response status is no content' response.status == HttpStatus.OK.value() where: 'the following registration json is used' @@ -181,7 +181,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { updatedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-2')], removedCmHandles: [CmHandleRegistrationResponse.createSuccessResponse('cm-handle-3')] ) - mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse + mockNetworkCmProxyInventoryFacade.updateDmiRegistration(*_) >> dmiRegistrationResponse when: 'registration endpoint is invoked' def response = mvc.perform( post("$ncmpBasePathV1/ch") @@ -205,7 +205,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { removedCmHandles: [removeCmHandleResponse], upgradedCmHandles: [upgradeCmHandleResponse] ) - mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> dmiRegistrationResponse + mockNetworkCmProxyInventoryFacade.updateDmiRegistration(*_) >> dmiRegistrationResponse when: 'registration endpoint is invoked' def response = mvc.perform( post("$ncmpBasePathV1/ch") diff --git a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy index e87acacc74..c75058bfd6 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyRestExceptionHandlerSpec.groovy @@ -35,7 +35,6 @@ import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade import org.onap.cps.ncmp.impl.data.NcmpCachedResourceRequestHandler import org.onap.cps.ncmp.impl.data.NcmpPassthroughResourceRequestHandler import org.onap.cps.ncmp.impl.data.NetworkCmProxyFacade -import org.onap.cps.ncmp.impl.data.policyexecutor.PolicyExecutor import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.rest.util.CmHandleStateMapper import org.onap.cps.ncmp.rest.util.DataOperationRequestMapper @@ -170,7 +169,7 @@ class NetworkCmProxyRestExceptionHandlerSpec extends Specification { if (NCMP == apiType) { mockNetworkCmProxyInventoryFacade.getYangResourcesModuleReferences(*_) >> { throw exception } } - mockNetworkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(*_) >> { throw exception } + mockNetworkCmProxyInventoryFacade.updateDmiRegistration(*_) >> { throw exception } } def performTestRequest(apiType) { diff --git a/cps-ncmp-service/pom.xml b/cps-ncmp-service/pom.xml index 9ce9d51363..fda4221f6f 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -74,10 +74,6 @@ <artifactId>cps-path-parser</artifactId> </dependency> <dependency> - <groupId>com.google.code.findbugs</groupId> - <artifactId>annotations</artifactId> - </dependency> - <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-spring</artifactId> </dependency> diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java index 5331c13bd1..e9e5f5499f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/api/inventory/NetworkCmProxyInventoryFacade.java @@ -62,15 +62,16 @@ public class NetworkCmProxyInventoryFacade { private final TrustLevelManager trustLevelManager; private final AlternateIdMatcher alternateIdMatcher; + + /** * Registration of Created, Removed, Updated or Upgraded CM Handles. * * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { - return cmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration); + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { + return cmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration); } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java index ad8025b5dc..109a541cb3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfig.java @@ -57,13 +57,13 @@ public class HazelcastCacheConfig { final Config config = getHazelcastInstanceConfig(instanceConfigName); config.setClusterName(clusterName); config.setClassLoader(org.onap.cps.spi.model.Dataspace.class.getClassLoader()); - dataStructuresConfig(namedConfig, config); + configureDataStructures(namedConfig, config); exposeClusterInformation(config); updateDiscoveryMode(config); return config; } - private static void dataStructuresConfig(final NamedConfig namedConfig, final Config config) { + private static void configureDataStructures(final NamedConfig namedConfig, final Config config) { if (namedConfig instanceof MapConfig) { config.addMapConfig((MapConfig) namedConfig); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java index d9f7e38993..cb55b09d41 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationService.java @@ -87,8 +87,7 @@ public class CmHandleRegistrationService { * @param dmiPluginRegistration Dmi Plugin Registration details * @return dmiPluginRegistrationResponse */ - public DmiPluginRegistrationResponse updateDmiRegistrationAndSyncModule( - final DmiPluginRegistration dmiPluginRegistration) { + public DmiPluginRegistrationResponse updateDmiRegistration(final DmiPluginRegistration dmiPluginRegistration) { dmiPluginRegistration.validateDmiPluginRegistration(); final DmiPluginRegistrationResponse dmiPluginRegistrationResponse = new DmiPluginRegistrationResponse(); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java index c6deb79d4d..e627f8f894 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasks.java @@ -95,22 +95,21 @@ public class ModuleSyncTasks { } /** - * Resets the state of failed CM handles and updates their status to ADVISED for retry. - - * This method processes a collection of failed CM handles, logs their lock reason, and resets their state + * Set the state of CM handles to ADVISED. + * This method processes a collection of CM handles, logs their lock reason, and resets their state * to ADVISED. Once reset, it updates the CM handle states in a batch to allow for re-attempt by the module-sync * watchdog. * - * @param failedCmHandles a collection of CM handles that have failed and need their state reset + * @param yangModelCmHandles a collection of CM handles that needs their state reset */ - public void resetFailedCmHandles(final Collection<YangModelCmHandle> failedCmHandles) { - final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(failedCmHandles.size()); - for (final YangModelCmHandle failedCmHandle : failedCmHandles) { - final CompositeState compositeState = failedCmHandle.getCompositeState(); - final String resetCmHandleId = failedCmHandle.getId(); + public void setCmHandlesToAdvised(final Collection<YangModelCmHandle> yangModelCmHandles) { + final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = new HashMap<>(yangModelCmHandles.size()); + for (final YangModelCmHandle yangModelCmHandle : yangModelCmHandles) { + final CompositeState compositeState = yangModelCmHandle.getCompositeState(); + final String resetCmHandleId = yangModelCmHandle.getId(); log.debug("Resetting CM handle {} state to ADVISED for retry by the module-sync watchdog. Lock reason: {}", - failedCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); - cmHandleStatePerCmHandle.put(failedCmHandle, CmHandleState.ADVISED); + yangModelCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); + cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED); removeResetCmHandleFromModuleSyncMap(resetCmHandleId); } lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java index bc7d6cdf67..898b8d5bf4 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdog.java @@ -27,10 +27,12 @@ import java.util.HashSet; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.utils.Sleeper; import org.onap.cps.spi.model.DataNode; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -45,6 +47,9 @@ public class ModuleSyncWatchdog { private final IMap<String, Object> moduleSyncStartedOnCmHandles; private final ModuleSyncTasks moduleSyncTasks; private final AsyncTaskExecutor asyncTaskExecutor; + private final Lock workQueueLock; + private final Sleeper sleeper; + private static final int MODULE_SYNC_BATCH_SIZE = 100; private static final long PREVENT_CPU_BURN_WAIT_TIME_MILLIS = 10; private static final String VALUE_FOR_HAZELCAST_IN_PROGRESS_MAP = "Started"; @@ -56,11 +61,12 @@ public class ModuleSyncWatchdog { * Check DB for any cm handles in 'ADVISED' state. * Queue and create batches to process them asynchronously. * This method will only finish when there are no more 'ADVISED' cm handles in the DB. - * This method wil be triggered on a configurable interval + * This method is triggered on a configurable interval (ncmp.timers.advised-modules-sync.sleep-time-ms) */ - @Scheduled(fixedDelayString = "${ncmp.timers.advised-modules-sync.sleep-time-ms:5000}") + @Scheduled(initialDelayString = "${test.ncmp.timers.advised-modules-sync.initial-delay-ms:0}", + fixedDelayString = "${ncmp.timers.advised-modules-sync.sleep-time-ms:5000}") public void moduleSyncAdvisedCmHandles() { - log.info("Processing module sync watchdog waking up."); + log.debug("Processing module sync watchdog waking up."); populateWorkQueueIfNeeded(); while (!moduleSyncWorkQueue.isEmpty()) { if (batchCounter.get() <= asyncTaskExecutor.getAsyncTaskParallelismLevel()) { @@ -80,36 +86,50 @@ public class ModuleSyncWatchdog { } /** - * Find any failed (locked) cm handles and change state back to 'ADVISED'. + * Populate work queue with advised cm handles from db. + * This method is made public for (integration) testing purposes. + * So it can be tested without the queue being emptied immediately as the main public method does. */ - @Scheduled(fixedDelayString = "${ncmp.timers.locked-modules-sync.sleep-time-ms:15000}") - public void resetPreviouslyFailedCmHandles() { - log.info("Processing module sync retry-watchdog waking up."); - final Collection<YangModelCmHandle> failedCmHandles - = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); - log.info("Retrying {} cmHandles", failedCmHandles.size()); - moduleSyncTasks.resetFailedCmHandles(failedCmHandles); + public void populateWorkQueueIfNeeded() { + if (moduleSyncWorkQueue.isEmpty() && workQueueLock.tryLock()) { + try { + populateWorkQueue(); + if (moduleSyncWorkQueue.isEmpty()) { + setPreviouslyLockedCmHandlesToAdvised(); + } + } finally { + workQueueLock.unlock(); + } + } } - private void preventBusyWait() { - try { - log.info("Busy waiting now"); - TimeUnit.MILLISECONDS.sleep(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); + private void populateWorkQueue() { + final Collection<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); + if (advisedCmHandles.isEmpty()) { + log.debug("No advised CM handles found in DB."); + } else { + log.info("Fetched {} advised CM handles from DB. Adding them to the work queue.", advisedCmHandles.size()); + advisedCmHandles.forEach(advisedCmHandle -> { + final String cmHandleId = String.valueOf(advisedCmHandle.getLeaves().get("id")); + if (moduleSyncWorkQueue.offer(advisedCmHandle)) { + log.info("CM handle {} added to the work queue.", cmHandleId); + } else { + log.warn("Failed to add CM handle {} to the work queue.", cmHandleId); + } + }); + log.info("Work queue contains {} items.", moduleSyncWorkQueue.size()); } } - private void populateWorkQueueIfNeeded() { - if (moduleSyncWorkQueue.isEmpty()) { - final Collection<DataNode> advisedCmHandles = moduleOperationsUtils.getAdvisedCmHandles(); - log.info("Processing module sync fetched {} advised cm handles from DB", advisedCmHandles.size()); - for (final DataNode advisedCmHandle : advisedCmHandles) { - if (!moduleSyncWorkQueue.offer(advisedCmHandle)) { - log.warn("Unable to add cm handle {} to the work queue", advisedCmHandle.getLeaves().get("id")); - } - } - log.info("Work Queue Size : {}", moduleSyncWorkQueue.size()); + private void setPreviouslyLockedCmHandlesToAdvised() { + final Collection<YangModelCmHandle> lockedCmHandles + = moduleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade(); + if (lockedCmHandles.isEmpty()) { + log.debug("No locked CM handles found in DB."); + } else { + log.info("Found {} Locked CM Handles. Changing state to Advise to retry syncing them again.", + lockedCmHandles.size()); + moduleSyncTasks.setCmHandlesToAdvised(lockedCmHandles); } } @@ -130,8 +150,16 @@ public class ModuleSyncWatchdog { nextBatch.add(batchCandidate); } } - log.debug("nextBatch size : {}", nextBatch.size()); + log.info("nextBatch size : {}", nextBatch.size()); return nextBatch; } + private void preventBusyWait() { + try { + log.debug("Busy waiting now"); + sleeper.haveALittleRest(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + } + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java index c5fae0d166..1f33cc349d 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/SynchronizationCacheConfig.java @@ -24,6 +24,7 @@ import com.hazelcast.config.MapConfig; import com.hazelcast.config.QueueConfig; import com.hazelcast.map.IMap; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.locks.Lock; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.impl.cache.HazelcastCacheConfig; import org.onap.cps.spi.model.DataNode; @@ -43,6 +44,7 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { private static final QueueConfig commonQueueConfig = createQueueConfig("defaultQueueConfig"); private static final MapConfig moduleSyncStartedConfig = createMapConfig("moduleSyncStartedConfig"); private static final MapConfig dataSyncSemaphoresConfig = createMapConfig("dataSyncSemaphoresConfig"); + private static final String LOCK_NAME_FOR_WORK_QUEUE = "workQueueLock"; /** * Module Sync Distributed Queue Instance. @@ -74,4 +76,21 @@ public class SynchronizationCacheConfig extends HazelcastCacheConfig { public IMap<String, Boolean> dataSyncSemaphores() { return getOrCreateHazelcastInstance(dataSyncSemaphoresConfig).getMap("dataSyncSemaphores"); } + + /** + * Retrieves a distributed lock used to control access to the work queue for module synchronization. + * This lock ensures that the population and modification of the work queue are thread-safe and + * protected from concurrent access across different nodes in the distributed system. + * The lock guarantees that only one instance of the application can populate or modify the + * module sync work queue at a time, preventing race conditions and potential data inconsistencies. + * The lock is obtained using the Hazelcast CP Subsystem's {@link Lock}, which provides + * strong consistency guarantees for distributed operations. + * + * @return a {@link Lock} instance used for synchronizing access to the work queue. + */ + @Bean + public Lock workQueueLock() { + // TODO Method below does not use commonQueueConfig for creating lock (Refactor later) + return getOrCreateHazelcastInstance(commonQueueConfig).getCPSubsystem().getLock(LOCK_NAME_FOR_WORK_QUEUE); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java new file mode 100644 index 0000000000..7a02fa06e0 --- /dev/null +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/Sleeper.java @@ -0,0 +1,35 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.impl.utils; + +import java.util.concurrent.TimeUnit; +import org.springframework.stereotype.Service; + +/** + * This class is to extract out sleep functionality so the interrupted exception handling can + * be covered with a test (e.g. using spy on Sleeper) and help to get to 100% code coverage. + */ +@Service +public class Sleeper { + public void haveALittleRest(final long timeInMillis) throws InterruptedException { + TimeUnit.MILLISECONDS.sleep(timeInMillis); + } +} diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java index d8e8350345..8ae942eb7b 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/utils/http/WebClientConfiguration.java @@ -20,7 +20,6 @@ package org.onap.cps.ncmp.impl.utils.http; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.netty.channel.ChannelOption; import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.handler.timeout.WriteTimeoutHandler; @@ -63,7 +62,6 @@ public class WebClientConfiguration { .compress(true); } - @SuppressFBWarnings("BC_UNCONFIRMED_CAST_OF_RETURN_VALUE") private static ConnectionProvider getConnectionProvider(final ServiceConfig serviceConfig) { return ConnectionProvider.builder(serviceConfig.getConnectionProviderName()) .maxConnections(serviceConfig.getMaximumConnectionsTotal()) diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy index dc38e0fc9b..0bd838437d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy @@ -29,10 +29,10 @@ class HazelcastCacheConfigSpec extends Specification { def objectUnderTest = new HazelcastCacheConfig() def 'Create Hazelcast instance with a #scenario'() { - given: 'a cluster name and instance name' + given: 'a cluster name and instance config name' objectUnderTest.clusterName = 'my cluster' objectUnderTest.instanceConfigName = 'my instance config' - when: 'an hazelcast instance is created (name has to be unique)' + when: 'a hazelcast instance is created (name has to be unique)' def result = objectUnderTest.getOrCreateHazelcastInstance(config) then: 'the instance is created and has the correct name' assert result.name == 'my instance config' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy index dcff2e9b89..70e26d993c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleRegistrationServiceSpec.groovy @@ -87,7 +87,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm handle is in READY state' mockCmHandleQueries.cmHandleHasState('cmhandle-3', CmHandleState.READY) >> true when: 'registration is processed' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'cm-handles are removed first' 1 * objectUnderTest.processRemovedCmHandles(*_) and: 'de-registered cm handle entry is removed from in progress map' @@ -108,7 +108,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'exception while checking cm handle state' mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> new YangModelCmHandle(id: 'cmhandle-3', moduleSetTag: '', compositeState: new CompositeState(cmHandleState: cmHandleState)) when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + def result = objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'upgrade operation contains expected error code' assert result.upgradedCmHandles[0].status == expectedResponseStatus where: 'the following parameters are used' @@ -124,7 +124,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'exception while checking cm handle state' mockInventoryPersistence.getYangModelCmHandle('cmhandle-3') >> { throw exception } when: 'registration is processed' - def result = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiRegistration) + def result = objectUnderTest.updateDmiRegistration(dmiRegistration) then: 'upgrade operation contains expected error code' assert result.upgradedCmHandles.ncmpResponseStatus.code[0] == expectedErrorCode where: 'the following parameters are used' @@ -139,7 +139,7 @@ class CmHandleRegistrationServiceSpec extends Specification { dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'update registration and sync module is called with correct DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'create cm handles registration and sync modules is called with the correct plugin information' 1 * objectUnderTest.processCreatedCmHandles(dmiPluginRegistration, _) where: @@ -155,7 +155,7 @@ class CmHandleRegistrationServiceSpec extends Specification { dmiDataPlugin: dmiDataPlugin) dmiPluginRegistration.createdCmHandles = [ncmpServiceCmHandle] when: 'registration is called with incorrect DMI plugin information' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a DMI Request Exception is thrown with correct message details' def exceptionThrown = thrown(DmiRequestException.class) assert exceptionThrown.getMessage().contains(expectedMessageDetails) @@ -178,7 +178,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server') dmiPluginRegistration.createdCmHandles = [new NcmpServiceCmHandle(cmHandleId: 'cmhandle', dmiProperties: dmiProperties, publicProperties: publicProperties)] when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a successful response is received' response.createdCmHandles.size() == 1 with(response.createdCmHandles[0]) { @@ -206,7 +206,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: 'my-server', createdCmHandles:[new NcmpServiceCmHandle(cmHandleId: 'ch-1', registrationTrustLevel: registrationTrustLevel)]) when: 'registration is updated' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'trustLevel is set for the created cm-handle' 1 * mockTrustLevelManager.registerCmHandles(expectedMapping) where: @@ -225,7 +225,7 @@ class CmHandleRegistrationServiceSpec extends Specification { def xpath = "somePathWithId[@id='cmhandle2']" mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw AlreadyDefinedException.forDataNodes([xpath], 'some-context') } when: 'registration is updated to create cm-handles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a response is received for all cm-handles' response.createdCmHandles.size() == 1 and: 'all cm-handles creation fails' @@ -244,7 +244,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handler registration fails: #scenario' mockLcmEventsCmHandleStateHandler.initiateStateAdvised(*_) >> { throw exception } when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a failure response is received' response.createdCmHandles.size() == 1 with(response.createdCmHandles[0]) { @@ -269,7 +269,7 @@ class CmHandleRegistrationServiceSpec extends Specification { CmHandleRegistrationResponse.createFailureResponse('cm handle 4', CM_HANDLE_INVALID_ID)] mockNetworkCmProxyDataServicePropertyHandler.updateCmHandleProperties(_) >> updateOperationResponse when: 'registration is updated' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the response contains updateOperationResponse' assert response.updatedCmHandles.size() == 4 assert response.updatedCmHandles.containsAll(updateOperationResponse) @@ -281,7 +281,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: '#scenario' mockCpsModuleService.deleteSchemaSetsWithCascade(_, ['cmhandle']) >> { if (!schemaSetExist) { throw new SchemaSetNotFoundException('', '') } } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the cmHandle state is updated to "DELETING"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> args[0].values()[0] == CmHandleState.DELETING } @@ -315,7 +315,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handle deletion is successful for 1st and 3rd; failed for 2nd' mockInventoryPersistence.deleteDataNode("/dmi-registry/cm-handles[@id='cmhandle2']") >> { throw new RuntimeException("Failed") } when: 'registration is updated to delete cmhandles' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the cmHandle states are all updated to "DELETING"' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch({ assert it.every { entry -> entry.value == CmHandleState.DELETING } }) and: 'a response is received for all cm-handles' @@ -361,7 +361,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'schema set single deletion failed with unknown error' mockInventoryPersistence.deleteSchemaSetWithCascade(_) >> { throw new RuntimeException('Failed') } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'no exception is thrown' noExceptionThrown() and: 'cm-handle is not deleted' @@ -387,7 +387,7 @@ class CmHandleRegistrationServiceSpec extends Specification { and: 'cm-handle deletion fails on individual delete' mockInventoryPersistence.deleteDataNode(_) >> { throw deleteListElementException } when: 'registration is updated to delete cmhandle' - def response = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def response = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'a failure response is received' assert response.removedCmHandles.size() == 1 with(response.removedCmHandles[0]) { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy index 42f0a08ac3..4c554c6af5 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -56,9 +56,9 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { given: 'an (updated) dmi plugin registration' def dmiPluginRegistration = Mock(DmiPluginRegistration) when: 'the registration is submitted ' - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'the call is delegated to the cm handle registration service' - 1 * mockCmHandleRegistrationService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + 1 * mockCmHandleRegistrationService.updateDmiRegistration(dmiPluginRegistration) } def 'Execute cm handle reference search for inventory'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index 160744a7d7..4d715d28c9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -136,7 +136,7 @@ class ModuleSyncTasksSpec extends Specification { moduleSyncStartedOnCmHandles.put('cm-handle-1', 'started') moduleSyncStartedOnCmHandles.put('cm-handle-2', 'started') when: 'resetting failed cm handles' - objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2]) + objectUnderTest.setCmHandlesToAdvised([yangModelCmHandle1, yangModelCmHandle2]) then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle) and: 'after reset performed progress map is empty' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy index 155edc8bc6..f2c88a511e 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncWatchdogSpec.groovy @@ -23,14 +23,16 @@ package org.onap.cps.ncmp.impl.inventory.sync import com.hazelcast.map.IMap import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.onap.cps.ncmp.impl.utils.Sleeper import org.onap.cps.spi.model.DataNode import spock.lang.Specification import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.locks.Lock class ModuleSyncWatchdogSpec extends Specification { - def mockSyncUtils = Mock(ModuleOperationsUtils) + def mockModuleOperationsUtils = Mock(ModuleOperationsUtils) def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE @@ -42,15 +44,23 @@ class ModuleSyncWatchdogSpec extends Specification { def spiedAsyncTaskExecutor = Spy(AsyncTaskExecutor) - def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor) + def mockWorkQueueLock = Mock(Lock) + + def spiedSleeper = Spy(Sleeper) + + def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock, spiedSleeper) void setup() { spiedAsyncTaskExecutor.setupThreadPool() } def 'Module sync advised cm handles with #scenario.'() { - given: 'sync utilities returns #numberOfAdvisedCmHandles advised cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + given: 'module sync utilities returns #numberOfAdvisedCmHandles advised cm handles' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(numberOfAdvisedCmHandles) + and: 'module sync utilities returns no failed (locked) cm handles' + mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> [] + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor has enough available threads' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 3 when: ' module sync is started' @@ -59,6 +69,7 @@ class ModuleSyncWatchdogSpec extends Specification { expectedNumberOfTaskExecutions * spiedAsyncTaskExecutor.executeTask(*_) where: 'the following parameter are used' scenario | numberOfAdvisedCmHandles || expectedNumberOfTaskExecutions + 'none at all' | 0 || 0 'less then 1 batch' | 1 || 1 'exactly 1 batch' | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 1 '2 batches' | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 2 @@ -66,9 +77,11 @@ class ModuleSyncWatchdogSpec extends Specification { 'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3 } - def 'Module sync advised cm handles starts with no available threads.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + def 'Module sync cm handles starts with no available threads.'() { + given: 'module sync utilities returns a advise cm handles' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor first has no threads but has one thread on the second attempt' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >>> [ 0, 1 ] when: ' module sync is started' @@ -77,9 +90,11 @@ class ModuleSyncWatchdogSpec extends Specification { 1 * spiedAsyncTaskExecutor.executeTask(*_) } - def 'Module sync advised cm handles already handled.'() { - given: 'sync utilities returns a advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(1) + def 'Module sync advised cm handle already handled by other thread.'() { + given: 'module sync utilities returns an advised cm handle' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'the work queue is not locked' + mockWorkQueueLock.tryLock() >> true and: 'the executor has a thread available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 1 and: 'the semaphore cache indicates the cm handle is already being processed' @@ -94,7 +109,7 @@ class ModuleSyncWatchdogSpec extends Specification { given: 'there is still a cm handle in the queue' moduleSyncWorkQueue.offer(new DataNode()) and: 'sync utilities returns many advise cm handles' - mockSyncUtils.getAdvisedCmHandles() >> createDataNodes(500) + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(500) and: 'the executor has plenty threads available' spiedAsyncTaskExecutor.getAsyncTaskParallelismLevel() >> 10 when: ' module sync is started' @@ -104,18 +119,42 @@ class ModuleSyncWatchdogSpec extends Specification { } def 'Reset failed cm handles.'() { - given: 'sync utilities returns failed cm handles' + given: 'module sync utilities returns failed cm handles' def failedCmHandles = [new YangModelCmHandle()] - mockSyncUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles + mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles when: 'reset failed cm handles is started' - objectUnderTest.resetPreviouslyFailedCmHandles() + objectUnderTest.setPreviouslyLockedCmHandlesToAdvised() then: 'it is delegated to the module sync task (service)' - 1 * mockModuleSyncTasks.resetFailedCmHandles(failedCmHandles) + 1 * mockModuleSyncTasks.setCmHandlesToAdvised(failedCmHandles) + } + + def 'Module Sync Locking.'() { + given: 'module sync utilities returns an advised cm handle' + mockModuleOperationsUtils.getAdvisedCmHandles() >> createDataNodes(1) + and: 'can lock is : #canLock' + mockWorkQueueLock.tryLock() >> canLock + when: 'attempt to populate the work queue' + objectUnderTest.populateWorkQueueIfNeeded() + then: 'the queue remains empty is #expectQueueRemainsEmpty' + assert moduleSyncWorkQueue.isEmpty() == expectQueueRemainsEmpty + where: 'the following lock states are applied' + canLock | expectQueueRemainsEmpty + false | true + true | false + } + + def 'Sleeper gets interrupted.'() { + given: 'sleeper gets interrupted' + spiedSleeper.haveALittleRest(_) >> { throw new InterruptedException() } + when: 'the watchdog attempts to sleep to save cpu cycles' + objectUnderTest.preventBusyWait() + then: 'no exception is thrown' + noExceptionThrown() } def createDataNodes(numberOfDataNodes) { def dataNodes = [] - (1..numberOfDataNodes).each {dataNodes.add(new DataNode())} + numberOfDataNodes.times { dataNodes.add(new DataNode()) } return dataNodes } } diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index 256e7a7dee..f97a40b223 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -32,7 +32,7 @@ <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <packaging>pom</packaging> <properties> diff --git a/cps-path-parser/pom.xml b/cps-path-parser/pom.xml index 7b015ad74f..57b1e6b247 100644 --- a/cps-path-parser/pom.xml +++ b/cps-path-parser/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-rest/docs/openapi/openapi.yml b/cps-rest/docs/openapi/openapi.yml index 70f06fbee2..95c32312cf 100644 --- a/cps-rest/docs/openapi/openapi.yml +++ b/cps-rest/docs/openapi/openapi.yml @@ -1,5 +1,5 @@ # ============LICENSE_START======================================================= -# Copyright (C) 2021-2023 Nordix Foundation +# Copyright (C) 2021-2024 Nordix Foundation # Modifications Copyright (C) 2021 Pantheon.tech # Modifications Copyright (C) 2021 Bell Canada. # Modifications Copyright (C) 2022-2024 TechMahindra Ltd. @@ -23,7 +23,7 @@ openapi: 3.0.3 info: title: ONAP Open API v3 Configuration Persistence Service description: Configuration Persistence Service is a Model Driven Generic Database - version: "3.5.2" + version: "3.5.4" contact: name: ONAP url: "https://onap.readthedocs.io" diff --git a/cps-rest/pom.xml b/cps-rest/pom.xml index 20fb299948..71395c2f0d 100644 --- a/cps-rest/pom.xml +++ b/cps-rest/pom.xml @@ -27,7 +27,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> @@ -196,6 +196,25 @@ </resources> </configuration> </execution> + <execution> + <id>copy-to-doc-folder</id> + <phase>compile</phase> + <goals> + <goal>copy-resources</goal> + </goals> + <configuration> + <outputDirectory>${project.basedir}/../docs/api/swagger/cps</outputDirectory> + <overwrite>true</overwrite> + <resources> + <resource> + <directory>${project.basedir}/target/generated-sources/openapi/</directory> + <includes> + <include>openapi.yaml</include> + </includes> + </resource> + </resources> + </configuration> + </execution> </executions> </plugin> </plugins> diff --git a/cps-ri/pom.xml b/cps-ri/pom.xml index bcf6c802de..1ab5920ef4 100644 --- a/cps-ri/pom.xml +++ b/cps-ri/pom.xml @@ -26,7 +26,7 @@ <parent>
<groupId>org.onap.cps</groupId>
<artifactId>cps-parent</artifactId>
- <version>3.5.4-SNAPSHOT</version>
+ <version>3.5.5-SNAPSHOT</version>
<relativePath>../cps-parent/pom.xml</relativePath>
</parent>
diff --git a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java index ee555f7dc8..ecbe447226 100644 --- a/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java +++ b/cps-ri/src/main/java/org/onap/cps/ri/CpsDataPersistenceServiceImpl.java @@ -228,6 +228,9 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService final Collection<String> xpaths = xpathToUpdatedDataNode.keySet(); Collection<FragmentEntity> existingFragmentEntities = getFragmentEntities(anchorEntity, xpaths); + + logMissingXPaths(xpaths, existingFragmentEntities); + existingFragmentEntities = fragmentRepository.prefetchDescendantsOfFragmentEntities( FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS, existingFragmentEntities); @@ -243,6 +246,19 @@ public class CpsDataPersistenceServiceImpl implements CpsDataPersistenceService } } + private void logMissingXPaths(final Collection<String> xpaths, final Collection<FragmentEntity> + existingFragmentEntities) { + final Set<String> existingXPaths = existingFragmentEntities.stream().map(FragmentEntity::getXpath) + .collect(Collectors.toSet()); + + final Set<String> missingXPaths = xpaths.stream().filter(xpath -> !existingXPaths.contains(xpath)) + .collect(Collectors.toSet()); + + if (!missingXPaths.isEmpty()) { + log.warn("Cannot update data nodes: Target XPaths {} not found in DB.", missingXPaths); + } + } + private void retryUpdateDataNodesIndividually(final AnchorEntity anchorEntity, final Collection<FragmentEntity> fragmentEntities) { final Collection<String> failedXpaths = new HashSet<>(); diff --git a/cps-service/pom.xml b/cps-service/pom.xml index 68cd206c96..c7a3666a07 100644 --- a/cps-service/pom.xml +++ b/cps-service/pom.xml @@ -29,7 +29,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java index 9859acdf0e..de57914527 100644 --- a/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java +++ b/cps-service/src/main/java/org/onap/cps/spi/model/DataNodeBuilder.java @@ -204,18 +204,17 @@ public class DataNodeBuilder { private static void addDataNodeFromNormalizedNode(final DataNode currentDataNode, final NormalizedNode normalizedNode) { - if (normalizedNode instanceof ChoiceNode) { - addChoiceNode(currentDataNode, (ChoiceNode) normalizedNode); - } else if (normalizedNode instanceof DataContainerNode) { - addYangContainer(currentDataNode, (DataContainerNode) normalizedNode); - } else if (normalizedNode instanceof MapNode) { - addDataNodeForEachListElement(currentDataNode, (MapNode) normalizedNode); - } else if (normalizedNode instanceof ValueNode) { - final ValueNode<NormalizedNode> valuesNode = (ValueNode) normalizedNode; - addYangLeaf(currentDataNode, valuesNode.getIdentifier().getNodeType().getLocalName(), - (Serializable) valuesNode.body()); - } else if (normalizedNode instanceof LeafSetNode) { - addYangLeafList(currentDataNode, (LeafSetNode<?>) normalizedNode); + if (normalizedNode instanceof ChoiceNode choiceNode) { + addChoiceNode(currentDataNode, choiceNode); + } else if (normalizedNode instanceof DataContainerNode dataContainerNode) { + addYangContainer(currentDataNode, dataContainerNode); + } else if (normalizedNode instanceof MapNode mapNode) { + addDataNodeForEachListElement(currentDataNode, mapNode); + } else if (normalizedNode instanceof ValueNode<?> valueNode) { + addYangLeaf(currentDataNode, valueNode.getIdentifier().getNodeType().getLocalName(), + (Serializable) valueNode.body()); + } else if (normalizedNode instanceof LeafSetNode<?> leafSetNode) { + addYangLeafList(currentDataNode, leafSetNode); } else { log.warn("Unsupported NormalizedNode type detected: {}", normalizedNode.getClass()); } @@ -243,7 +242,7 @@ public class DataNodeBuilder { private static void addYangLeafList(final DataNode currentDataNode, final LeafSetNode<?> leafSetNode) { final String leafListName = leafSetNode.getIdentifier().getNodeType().getLocalName(); - List<?> leafListValues = ((Collection<? extends NormalizedNode>) leafSetNode.body()) + List<?> leafListValues = (leafSetNode.body()) .stream() .map(NormalizedNode::body) .collect(Collectors.toList()); diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index e7703d8d68..b854064ca5 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -147,7 +147,7 @@ services: KAFKA_BOOTSTRAP_SERVER: kafka:29092 NCMP_CONSUMER_GROUP_ID: ncmp-group NCMP_ASYNC_M2M_TOPIC: ncmp-async-m2m - MODULE_INITIAL_PROCESSING_DELAY_MS: 0 + MODULE_INITIAL_PROCESSING_DELAY_MS: 120000 MODULE_REFERENCES_DELAY_MS: 100 MODULE_RESOURCES_DELAY_MS: 1000 READ_DATA_FOR_CM_HANDLE_DELAY_MS: 300 diff --git a/docs/api/swagger/cps/openapi.yaml b/docs/api/swagger/cps/openapi.yaml index d22b2887a6..3069b18d1e 100644 --- a/docs/api/swagger/cps/openapi.yaml +++ b/docs/api/swagger/cps/openapi.yaml @@ -9,7 +9,7 @@ info: name: Apache 2.0 url: http://www.apache.org/licenses/LICENSE-2.0 title: ONAP Open API v3 Configuration Persistence Service - version: 3.5.2 + version: 3.5.4 servers: - url: /cps/api security: @@ -1451,6 +1451,15 @@ paths: schema: default: / type: string + - description: "Boolean flag to validate data, without persisting it. Default\ + \ value is set to false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean - description: observed-timestamp in: query name: observed-timestamp @@ -2550,6 +2559,16 @@ components: schema: example: application/json type: string + dryRunInQuery: + description: "Boolean flag to validate data, without persisting it. Default\ + \ value is set to false." + in: query + name: dry-run + required: false + schema: + default: false + example: false + type: boolean requiredXpathInQuery: description: "For more details on xpath, please refer https://docs.onap.org/projects/onap-cps/en/latest/xpath.html" examples: diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index d710316fc3..a2c7af6fda 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: description: NCMP Inventory API title: NCMP Inventory API - version: 3.5.2 + version: 3.5.4 servers: - url: /ncmpInventory security: diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index 871090fc7c..f93395a6db 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: description: NCMP to CPS Proxy API title: NCMP to CPS Proxy API - version: 3.5.2 + version: 3.5.4 servers: - url: /ncmp security: diff --git a/docs/cps-scheduled-processes.rst b/docs/cps-scheduled-processes.rst index 032b4b143b..c204e6ca0a 100644 --- a/docs/cps-scheduled-processes.rst +++ b/docs/cps-scheduled-processes.rst @@ -22,9 +22,11 @@ Module Sync ----------- The module sync is a user :ref:`configurable timed process<additional-cps-ncmp-customizations>`, which is set to search for CM-Handles within CPS with an *'ADVISED'* state. -Once the CM-Handle(s) is processed by the module sync, the CM-Handle state is then set to *'READY'*, if the process completes successfully. +Once the CM-Handle is processed by the module sync, the CM-Handle state is then set to *'READY'*, if the process completes successfully. If for any reason the module sync fails, the CM-Handle state will then be set to *'LOCKED'*, and the reason for the lock will also be stored within CPS. +CM-Handles in the *'LOCKED'* state will be retried when the system has availability. CM-Handles in a *'LOCKED'* +state are processed by the retry mechanism, by setting CM-Handle state back to *'ADVISED'* so the next sync cycle will process those again. Data Sync --------- @@ -33,13 +35,3 @@ which is set to search for CM-Handles with a sync state of *'UNSYNCHRONIZED'*. Once the CM-Handle(s) with a sync state of *'UNSYNCHRONIZED'* is processed by the data sync, the CM-Handle sync state is then set to *'SYNCHRONIZED'*, if the process completes successfully. If the data sync fails, the CM-Handle sync state will remain as *'UNSYNCHRONIZED'*, and will be re-attempted. - -Retry Mechanism ---------------- -The retry mechanism is a user :ref:`configurable timed process<additional-cps-ncmp-customizations>`, -which is used to search for CM-Handles which are currently in a *'LOCKED'* state. -If the CM-Handle is ready to be retried then, the CM-Handle(s) in a *'LOCKED'* state is processed by the retry mechanism, -the CM-Handle state is then set to *'ADVISED'*. -Whether the CM-Handle is ready to be retried is dependent on both the number of attempts to sync the CM-Handle, -and the last update time of the CM-Handle state. -With each new attempt to unlock the CM-Handle, the time until the CM-Handle can next be retried is doubled. diff --git a/docs/deployment.rst b/docs/deployment.rst index ba8fcd9347..3f0c1eddc5 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -19,6 +19,24 @@ CPS uses PostgreSQL database. As per the `PostgreSQL documentation on resource c parameter should be set between 25% and 40% of total memory. It has a default value of 128 megabytes, so this should be set appropriately. For example, given a database with 2GB of memory, 512MB is a recommended value. +CPS and NCMP Configuration +========================== + +JVM Memory Allocation + +Allocating 75% of the container's memory to the JVM heap ensures efficient memory management. +This helps the JVM make the best use of the allocated resources while leaving enough memory for other processes. + +.. code-block:: yaml + + JAVA_TOOL_OPTIONS: "-XX:InitialRAMPercentage=75.0 -XX:MaxRAMPercentage=75.0" + +Load balancer configuration +=========================== + +For optimal performance in CPS/NCMP, load balancers should be configured to use a least-requests policy, also known as +least-connected. Use of round-robin load balancing can lead to instability. + CPS OOM Charts ============== The CPS kubernetes chart is located in the `OOM repository <https://github.com/onap/oom/tree/master/kubernetes/cps>`_. @@ -284,20 +302,15 @@ Additional CPS-NCMP Customizations | | | | | | See also :ref:`cps_common_credentials_retrieval`. | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.ncmp.timers | Specifies the delay in milliseconds in which the module sync watch dog will wake again after finishing. | ``30000`` | +| config.ncmp.timers | Specifies the delay in milliseconds in which the module sync watch dog will wake again after finishing. | ``5000`` | | .advised-modules-sync.sleep-time-ms | | | | | | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.ncmp.timers | Specifies the delay in milliseconds in which the retry mechanism watch dog | | -| .locked-modules-sync.sleep-time-ms | will wake again after finishing. | ``300000`` | -| | | | -| | | | -+-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ | config.ncmp.timers | Specifies the delay in milliseconds in which the data sync watch dog will wake again after finishing. | ``30000`` | | .cm-handle-data-sync.sleep-time-ms | | | | | | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ -| config.additional.ncmp.dmi.httpclient | Specifies the maximum time in seconds, to wait for establishing a connection for the HTTP Client. | ``180`` | +| config.additional.ncmp.dmi.httpclient | Specifies the maximum time in seconds, to wait for establishing a connection for the HTTP Client. | ``30`` | | .connectionTimeoutInSeconds | | | +-------------------------------------------+---------------------------------------------------------------------------------------------------------+-------------------------------+ | config.additional.ncmp.dmi.httpclient | Specifies the maximum number of connections allowed per route in the HTTP client. | ``50`` | @@ -319,32 +332,27 @@ The latest instructions are covered in the `README <https://github.com/onap/cps/ .. Below Label is used by documentation for other CPS components to link here, do not remove even if it gives a warning .. _cps_common_distributed_datastructures: -CPS-Core and NCMP Distributed Datastructures -============================================ +NCMP Distributed Data Structures +================================ -CPS-Core and NCMP both internally uses embedded distributed datastructure to replicate the state across various instances for low latency. -These instances require some additional ports to be available. The default range starts from 5701 and based on the number of instances configured they are incremented sequentially. +NCMP utilizes embedded distributed data structures to replicate state across various instances, ensuring low latency and high performance. Each JVM runs a Hazelcast instance to manage these data structures. To facilitate member visibility and cluster formation, an additional port (defaulting to 5701) must be available. Below are the list of distributed datastructures that we have. +--------------+------------------------------------+-----------------------------------------------------------+ -| Component | Datastructure name | Use | +| Component | Data Structure Name | Use | +==============+====================================+===========================================================+ -| cps-core | anchorDataCache | Used to resolve prefix for the container name. | -+--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | moduleSyncStartedOnCmHandles | Watchdog process to register cm handles. | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | dataSyncSemaphores | Watchdog process to sync data from the nodes. | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | moduleSyncWorkQueue | Queue used internally for workers to pick the task. | +--------------+------------------------------------+-----------------------------------------------------------+ -| cps-ncmp | untrustworthyCmHandlesSet | Stores untrustworthy cm handles whose trust level is NONE.| +| cps-ncmp | trustLevelPerCmHandle | Stores the trust level per cm handle id | +--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | trustLevelPerDmiPlugin | Stores the trust level for the dmi-plugins. | +--------------+------------------------------------+-----------------------------------------------------------+ -| cps-ncmp | moduleSetTagCacheMapConfig | Stores the module set tags for cm handles. | -+--------------+------------------------------------+-----------------------------------------------------------+ | cps-ncmp | cmNotificationSubscriptionCache | Stores and tracks cm notification subscription requests. | +--------------+------------------------------------+-----------------------------------------------------------+ -Total number of caches : 8 +Total number of caches : 6 diff --git a/docs/release-notes.rst b/docs/release-notes.rst index cebb1c0800..c5b00d6888 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -16,6 +16,34 @@ CPS Release Notes .. * * * OSLO * * * .. ==================== +Version: 3.5.5 +============== + +Release Data +------------ + ++--------------------------------------+--------------------------------------------------------+ +| **CPS Project** | | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Docker images** | onap/cps-and-ncmp:3.5.5 | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release designation** | 3.5.5 Oslo | +| | | ++--------------------------------------+--------------------------------------------------------+ +| **Release date** | Not yet released | +| | | ++--------------------------------------+--------------------------------------------------------+ + +Bug Fixes +--------- +3.5.5 + +Features +-------- +3.5.5 + Version: 3.5.4 ============== @@ -32,17 +60,19 @@ Release Data | **Release designation** | 3.5.4 Oslo | | | | +--------------------------------------+--------------------------------------------------------+ -| **Release date** | Not yet released | +| **Release date** | 2024 October 17 | | | | +--------------------------------------+--------------------------------------------------------+ Bug Fixes --------- 3.5.4 + - `CPS-2403 <https://lf-onap.atlassian.net/browse/CPS-2403>`_ Improve lock handling and queue management during CM-handle Module Sync. Features -------- 3.5.4 + - `CPS-2408 <https://lf-onap.atlassian.net/browse/CPS-2408>`_ One Hazelcast instance per JVM to manage the distributed data structures. Version: 3.5.3 ============== @@ -1588,10 +1618,6 @@ Following CPS components are available with default ONAP/CPS installation. - Postgres Database -Below service components (mS) are available to be deployed on-demand. - - CPS-TBDMT - - Under OOM (Kubernetes) all CPS component containers are deployed as Kubernetes Pods/Deployments/Services into Kubernetes cluster. Known Limitations, Issues and Workarounds @@ -1701,10 +1727,6 @@ Following CPS components are available with default ONAP/CPS installation. - Postgres Database -Below service components (mS) are available to be deployed on-demand. - - CPS-TBDMT - - Under OOM (Kubernetes) all CPS component containers are deployed as Kubernetes Pods/Deployments/Services into Kubernetes cluster. diff --git a/integration-test/pom.xml b/integration-test/pom.xml index b26ec5b0c9..b39c1987bf 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> 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 587cbae619..759eccd966 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 @@ -53,6 +53,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.annotation.ComponentScan import org.springframework.data.jpa.repository.config.EnableJpaRepositories +import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.servlet.MockMvc import org.testcontainers.spock.Testcontainers import spock.lang.Shared @@ -61,6 +62,7 @@ import spock.util.concurrent.PollingConditions import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import java.util.concurrent.BlockingQueue import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -73,6 +75,7 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY @EnableJpaRepositories(basePackageClasses = [DataspaceRepository]) @ComponentScan(basePackages = ['org.onap.cps']) @EntityScan('org.onap.cps.ri.models') +@ActiveProfiles('module-sync-delayed') abstract class CpsIntegrationSpecBase extends Specification { @Shared @@ -118,6 +121,9 @@ abstract class CpsIntegrationSpecBase extends Specification { ModuleSyncWatchdog moduleSyncWatchdog @Autowired + BlockingQueue<DataNode> moduleSyncWorkQueue + + @Autowired JsonObjectMapper jsonObjectMapper @Autowired @@ -244,26 +250,39 @@ abstract class CpsIntegrationSpecBase extends Specification { } def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, alternateId) { - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag, alternateId: alternateId) - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate])) + registerCmHandleWithoutWaitForReady(dmiPlugin, cmHandleId, moduleSetTag, alternateId) + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { CmHandleState.READY == networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleId).cmHandleState }) } + def registerCmHandleWithoutWaitForReady(dmiPlugin, cmHandleId, moduleSetTag, alternateId) { + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag, alternateId: alternateId) + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate])) + } + + def registerSequenceOfCmHandlesWithoutWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles) { + def cmHandles = [] + (1..numberOfCmHandles).each { + def cmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch-'+it, moduleSetTag: moduleSetTag, alternateId: NO_ALTERNATE_ID) + cmHandles.add(cmHandle) + } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: cmHandles)) + } + def deregisterCmHandle(dmiPlugin, cmHandleId) { deregisterCmHandles(dmiPlugin, [cmHandleId]) } def deregisterCmHandles(dmiPlugin, cmHandleIds) { - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } - def overrideCmHandleLastUpdateTime(cmHandleId, newUpdateTime) { - String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; - DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); - def jsonForUpdate = '{ "state": { "last-update-time": "%s" } }'.formatted(ISO_TIMESTAMP_FORMATTER.format(newUpdateTime)) - cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='${cmHandleId}']", jsonForUpdate, now, ContentType.JSON) + def deregisterSequenceOfCmHandles(dmiPlugin, numberOfCmHandles) { + def cmHandleIds = [] + (1..numberOfCmHandles).each { cmHandleIds.add('ch-'+it) } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } + } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy index 5f4ba3456c..9e51d80d9e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy @@ -134,7 +134,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase { objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchema1', 'newSchema2']) } - def 'Create schema set error scenario: #scenario.'() { + def 'Attempt to create schema set, error scenario: #scenario.'() { when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName' populateNewYangResourcesNameToContentMapAndAllModuleReferences(0) objectUnderTest.createSchemaSet(dataspaceName, schemaSetName, newYangResourcesNameToContentMap) @@ -146,6 +146,14 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase { 'schema set already exists' | FUNCTIONAL_TEST_DATASPACE_1 | BOOKSTORE_SCHEMA_SET || AlreadyDefinedException } + def 'Attempt to create duplicate schema set from modules.'() { + when: 'attempt to store duplicate schema set from modules' + objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, newYangResourcesNameToContentMap, []) + then: 'an Already Defined Exception is thrown' + thrown(AlreadyDefinedException) + } + + /* R E A D S C H E M A S E T I N F O U S E - C A S E S */ diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy index 10a9f15e21..19b10a3c79 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy @@ -32,42 +32,48 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.v1.LcmEvent import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import spock.lang.Ignore import spock.util.concurrent.PollingConditions import java.time.Duration -import java.time.OffsetDateTime class CmHandleCreateSpec extends CpsIntegrationSpecBase { NetworkCmProxyInventoryFacade objectUnderTest + def uniqueId = 'ch-unique-id-for-create-test' - def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class) + def kafkaConsumer = KafkaTestContainer.getConsumer('test-group', StringDeserializer.class) def setup() { objectUnderTest = networkCmProxyInventoryFacade } + @Ignore def 'CM Handle registration is successful.'() { given: 'DMI will return modules when requested' dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId[uniqueId] = ['M1', 'M2'] and: 'consumer subscribed to topic' kafkaConsumer.subscribe(['ncmp-events']) when: 'a CM-handle is registered for creation' - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: uniqueId) def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'registration gives successful response' - assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] + assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(uniqueId)] and: 'CM-handle is initially in ADVISED state' - assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState + assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() and: 'CM-handle goes to READY state after module sync' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState }) and: 'the messages is polled' @@ -76,13 +82,20 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { and: 'the newest lcm event notification is received with READY state' def notificationMessage = jsonObjectMapper.convertJsonString(records.last().value().toString(), LcmEvent) + /*TODO (Toine) This test was failing intermittently (when running as part of suite). + I suspect that it often gave false positives as the message being assert here was any random message created by previous tests + By checking the cm-handle and using an unique cm-handle in this test this flaw became obvious. + I have now ignored this test as it is out of scope of this commit to fix it. + Created: https://lf-onap.atlassian.net/browse/CPS-2468 to fix this instead + */ + assert notificationMessage.event.cmHandleId == uniqueId assert notificationMessage.event.newValues.cmHandleState.value() == 'READY' and: 'the CM-handle has expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(uniqueId).moduleName.sort() cleanup: 'deregister CM handle' - deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, uniqueId) } def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() { @@ -92,7 +105,10 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { when: 'a CM-handle is registered for creation' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -117,7 +133,10 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { when: 'a CM-handle is registered for creation with moduleSetTag "B"' def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B') - objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'the CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -151,7 +170,7 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { new NcmpServiceCmHandle(cmHandleId: 'ch-7', alternateId: 'duplicate-alt-id'), ] def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) then: 'registration gives expected responses' assert dmiPluginRegistrationResponse.createdCmHandles.sort { it.cmHandle } == [ @@ -173,7 +192,11 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { when: 'CM-handles are registered for creation' def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')] def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + objectUnderTest.updateDmiRegistration(dmiPluginRegistration) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() + then: 'CM-handles go to LOCKED state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.LOCKED @@ -183,6 +206,9 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M2']] dmiDispatcher1.isAvailable = true + and: 'the module sync watchdog is triggered TWICE' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + then: 'Both CM-handles go to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { ['ch-1', 'ch-2'].each { cmHandleId -> diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy index 2d1588ecf9..67011f811b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy @@ -44,7 +44,7 @@ class CmHandleUpdateSpec extends CpsIntegrationSpecBase { when: "CM-handle is registered for update with new alternate ID: $newAlternateId" def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: newAlternateId) def dmiPluginRegistrationResponse = - objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) then: 'registration gives successful response' assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] @@ -74,7 +74,7 @@ class CmHandleUpdateSpec extends CpsIntegrationSpecBase { when: 'a CM-handle is registered for update with new alternate ID' def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'new') def dmiPluginRegistrationResponse = - objectUnderTest.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) then: 'registration gives failure response, due to alternate ID being already associated' assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createFailureResponse('ch-1', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED)] diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy index f93f58ce20..64449371fe 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy @@ -48,7 +48,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule( + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' @@ -63,6 +63,9 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: 'DMI will return different modules for upgrade: M1 and M3' dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3'] + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + then: 'CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState @@ -98,12 +101,15 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: "CM-handle is upgraded to moduleSetTag '${updatedModuleSetTag}'" def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule( + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)] + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + and: 'CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState @@ -132,7 +138,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: 'CM-handle is upgraded with the same moduleSetTag' def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'same') - objectUnderTest.updateDmiRegistrationAndSyncModule( + objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'CM-handle remains in READY state' @@ -157,9 +163,12 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { when: 'the CM-handle is upgraded' def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag') - objectUnderTest.updateDmiRegistrationAndSyncModule( + objectUnderTest.updateDmiRegistration( new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID) diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy new file mode 100644 index 0000000000..e0bb437a7c --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy @@ -0,0 +1,99 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * 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.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog + +import java.util.concurrent.Executors + +class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase { + + ModuleSyncWatchdog objectUnderTest + + def executorService = Executors.newFixedThreadPool(2) + def SYNC_SAMPLE_SIZE = 100 + + def setup() { + objectUnderTest = moduleSyncWatchdog + registerSequenceOfCmHandlesWithoutWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, SYNC_SAMPLE_SIZE) + } + + def cleanup() { + try { + deregisterSequenceOfCmHandles(DMI1_URL, SYNC_SAMPLE_SIZE) + moduleSyncWorkQueue.clear() + } finally { + executorService.shutdownNow() + } + } + + def 'Watchdog is disabled for test.'() { + when: 'wait a while but less then the initial delay of 10 minutes' + Thread.sleep(3000) + then: 'the work queue remains empty' + assert moduleSyncWorkQueue.isEmpty() + } + + def 'Populate module sync work queue simultaneously on two parallel threads (CPS-2403).'() { + // This test failed before bug https://lf-onap.atlassian.net/browse/CPS-2403 was fixed + given: 'the queue is empty at the start' + assert moduleSyncWorkQueue.isEmpty() + when: 'attempt to populate the queue on the main (test) and another parallel thread at the same time' + objectUnderTest.populateWorkQueueIfNeeded() + executorService.execute(populateQueueWithoutDelay) + and: 'wait a little (to give all threads time to complete their task)' + Thread.sleep(50) + then: 'the queue size is exactly the sample size' + assert moduleSyncWorkQueue.size() == SYNC_SAMPLE_SIZE + } + + def 'Populate module sync work queue on two parallel threads with a slight difference in start time.'() { + // This test proved that the issue in CPS-2403 did not arise if the the queue was populated and given time to be distributed + given: 'the queue is empty at the start' + assert moduleSyncWorkQueue.isEmpty() + when: 'attempt to populate the queue on the main (test) and another parallel thread a little later' + objectUnderTest.populateWorkQueueIfNeeded() + executorService.execute(populateQueueWithDelay) + and: 'wait a little (to give all threads time to complete their task)' + Thread.sleep(50) + then: 'the queue size is exactly the sample size' + assert moduleSyncWorkQueue.size() == SYNC_SAMPLE_SIZE + } + + def populateQueueWithoutDelay = () -> { + try { + objectUnderTest.populateWorkQueueIfNeeded() + } catch (InterruptedException e) { + e.printStackTrace() + } + } + + def populateQueueWithDelay = () -> { + try { + Thread.sleep(10) + objectUnderTest.populateWorkQueueIfNeeded() + } catch (InterruptedException e) { + e.printStackTrace() + } + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy index 179126c1a6..7ce3cf5e17 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy @@ -44,6 +44,8 @@ class RestApiSpec extends CpsIntegrationSpecBase { def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) .andExpect(status().is2xxSuccessful()) + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { (1..3).each { 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 f6ae27d129..fb5a0c3eb1 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 @@ -27,11 +27,13 @@ import org.onap.cps.utils.ContentType import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR +import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_PARENT class NcmpPerfTestBase extends PerfTestBase { def static NCMP_PERFORMANCE_TEST_DATASPACE = 'ncmpPerformance' - def static REGISTRY_ANCHOR = 'ncmp-registry' + def static REGISTRY_ANCHOR = NCMP_DMI_REGISTRY_ANCHOR + def static REGISTRY_PARENT = NCMP_DMI_REGISTRY_PARENT def static REGISTRY_SCHEMA_SET = 'registrySchemaSet' def static TOTAL_CM_HANDLES = 20_000 def static CM_DATA_SUBSCRIPTIONS_ANCHOR = 'cm-data-subscriptions' @@ -70,30 +72,30 @@ class NcmpPerfTestBase extends PerfTestBase { } def createRegistrySchemaSet() { - def modelAsString = readResourceDataFile('ncmp-registry/dmi-registry@2024-02-23.yang') + def modelAsString = readResourceDataFile('inventory/dmi-registry@2024-02-23.yang') cpsModuleService.createSchemaSet(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, [registry: modelAsString]) } def addRegistryData() { cpsAnchorService.createAnchor(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_SCHEMA_SET, REGISTRY_ANCHOR) cpsDataService.saveData(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '{"dmi-registry": []}', now) - def innerNodeJsonTemplate = readResourceDataFile('ncmp-registry/innerNode.json') + def cmHandleJsonTemplate = readResourceDataFile('inventory/cmHandleTemplate.json') def batchSize = 100 for (def i = 0; i < TOTAL_CM_HANDLES; i += batchSize) { - def data = '{ "cm-handles": [' + (1..batchSize).collect { innerNodeJsonTemplate.replace('CMHANDLE_ID_HERE', (it + i).toString()) }.join(',') + ']}' - cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON) + def data = '{ "cm-handles": [' + (1..batchSize).collect { cmHandleJsonTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) }.join(',') + ']}' + cpsDataService.saveListElements(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, REGISTRY_PARENT, data, now, ContentType.JSON) } } def addRegistryDataWithAlternateIdAsPath() { - def innerNodeJsonTemplate = readResourceDataFile('ncmp-registry/innerCmHandleNode.json') + def cmHandleWithAlternateIdTemplate = readResourceDataFile('inventory/cmHandleWithAlternateIdTemplate.json') def batchSize = 10 for (def i = 0; i < TOTAL_CM_HANDLES; i += batchSize) { def data = '{ "cm-handles": [' + (1..batchSize).collect { - innerNodeJsonTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) + cmHandleWithAlternateIdTemplate.replace('CM_HANDLE_ID_HERE', (it + i).toString()) .replace('ALTERNATE_ID_AS_PATH', (it + i).toString()) }.join(',') + ']}' - cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, '/dmi-registry', data, now, ContentType.JSON) + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, REGISTRY_PARENT, data, now, ContentType.JSON) } } @@ -117,7 +119,7 @@ class NcmpPerfTestBase extends PerfTestBase { def result = cpsDataService.getDataNodes(NCMP_PERFORMANCE_TEST_DATASPACE, REGISTRY_ANCHOR, '/', FetchDescendantsOption.OMIT_DESCENDANTS) resourceMeter.stop() then: 'expected data exists' - assert result.xpath == ['/dmi-registry'] + assert result.xpath == [REGISTRY_PARENT] and: 'operation completes within expected time' recordAndAssertResourceUsage('NCMP pre-load test data', 15, resourceMeter.totalTimeInSeconds, diff --git a/integration-test/src/test/resources/application-module-sync-delayed.yml b/integration-test/src/test/resources/application-module-sync-delayed.yml new file mode 100644 index 0000000000..7b9c6aea4f --- /dev/null +++ b/integration-test/src/test/resources/application-module-sync-delayed.yml @@ -0,0 +1,23 @@ +# ============LICENSE_START======================================================= +# Copyright (C) 2024 Nordix Foundation. +# ================================================================================ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# 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========================================================= + +test: + ncmp: + timers: + advised-modules-sync: + initial-delay-ms: 600000 + diff --git a/integration-test/src/test/resources/application.yml b/integration-test/src/test/resources/application.yml index f9958651d7..b786a3d4c5 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -179,9 +179,7 @@ ncmp: timers: advised-modules-sync: - sleep-time-ms: 1000 - locked-modules-sync: - sleep-time-ms: 1000 + sleep-time-ms: 1000000 cm-handle-data-sync: sleep-time-ms: 30000 subscription-forwarding: diff --git a/integration-test/src/test/resources/data/ncmp-registry/innerNode.json b/integration-test/src/test/resources/data/inventory/cmHandleTemplate.json index b6c65f3763..6577f4e560 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/innerNode.json +++ b/integration-test/src/test/resources/data/inventory/cmHandleTemplate.json @@ -1,6 +1,6 @@ { - "id": "cm-CMHANDLE_ID_HERE", - "alternate-id": "alt-CMHANDLE_ID_HERE", + "id": "cm-CM_HANDLE_ID_HERE", + "alternate-id": "alt-CM_HANDLE_ID_HERE", "module-set-tag": "my-module-set-tag", "dmi-service-name": "http://ncmp-dmi-plugin-stub:8080", "dmi-data-service-name": "", @@ -21,4 +21,4 @@ } } } -}
\ No newline at end of file +} diff --git a/integration-test/src/test/resources/data/ncmp-registry/innerCmHandleNode.json b/integration-test/src/test/resources/data/inventory/cmHandleWithAlternateIdTemplate.json index 88446c4a0f..88446c4a0f 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/innerCmHandleNode.json +++ b/integration-test/src/test/resources/data/inventory/cmHandleWithAlternateIdTemplate.json diff --git a/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2024-02-23.yang b/integration-test/src/test/resources/data/inventory/dmi-registry@2024-02-23.yang index d7b4ff7550..d7b4ff7550 100644 --- a/integration-test/src/test/resources/data/ncmp-registry/dmi-registry@2024-02-23.yang +++ b/integration-test/src/test/resources/data/inventory/dmi-registry@2024-02-23.yang diff --git a/jacoco-report/pom.xml b/jacoco-report/pom.xml index 298a0e76bd..1f43153977 100644 --- a/jacoco-report/pom.xml +++ b/jacoco-report/pom.xml @@ -5,7 +5,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> diff --git a/k6-tests/ncmp/common/search-base.js b/k6-tests/ncmp/common/search-base.js index 1e98e9b07b..af2caf71ec 100644 --- a/k6-tests/ncmp/common/search-base.js +++ b/k6-tests/ncmp/common/search-base.js @@ -43,7 +43,7 @@ const SEARCH_PARAMETERS_PER_SCENARIO = { "cmHandleQueryParameters": [ { "conditionName": "hasAllModules", - "conditionParameters": [{"moduleName": "ietf-yang-types"}] + "conditionParameters": [{"moduleName": "module100"}] } ] }, diff --git a/k6-tests/ncmp/common/utils.js b/k6-tests/ncmp/common/utils.js index 7873732361..6ddcca5fbf 100644 --- a/k6-tests/ncmp/common/utils.js +++ b/k6-tests/ncmp/common/utils.js @@ -30,7 +30,7 @@ export const LEGACY_BATCH_THROUGHPUT_TEST_BATCH_SIZE = 200; export const LEGACY_BATCH_THROUGHPUT_TEST_NUMBER_OF_REQUESTS = 1000; export const LEGACY_BATCH_TOPIC_NAME = 'legacy_batch_topic'; export const KAFKA_BOOTSTRAP_SERVERS = ['localhost:9092']; -export const MODULE_SET_TAGS = ['tagA','tagB','tagC',' tagD'] +export const MODULE_SET_TAGS = ['tagA', 'tagB', 'tagC', 'tagD', 'tagE']; /** @@ -85,7 +85,7 @@ export function makeCustomSummaryReport(testResults, scenarioConfig) { const summaryCsvLines = [ '#,Test Name,Unit,Fs Requirement,Current Expectation,Actual', makeSummaryCsvLine('0', 'HTTP request failures for all tests', 'rate of failed requests', 'http_req_failed', 0, testResults, scenarioConfig), - makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', 110, testResults, scenarioConfig), + makeSummaryCsvLine('1', 'Registration of CM-handles', 'CM-handles/second', 'cmhandles_created_per_second', 70, testResults, scenarioConfig), makeSummaryCsvLine('2', 'De-registration of CM-handles', 'CM-handles/second', 'cmhandles_deleted_per_second', 90, testResults, scenarioConfig), makeSummaryCsvLine('3a', 'CM-handle ID search with No filter', 'milliseconds', 'id_search_nofilter_duration', 400, testResults, scenarioConfig), makeSummaryCsvLine('3b', 'CM-handle ID search with Module filter', 'milliseconds', 'id_search_module_duration', 200, testResults, scenarioConfig), diff --git a/k6-tests/ncmp/ncmp-kpi.js b/k6-tests/ncmp/ncmp-kpi.js index 8815511bdd..a8a6a09d95 100644 --- a/k6-tests/ncmp/ncmp-kpi.js +++ b/k6-tests/ncmp/ncmp-kpi.js @@ -58,8 +58,8 @@ const DURATION = '15m'; const LEGACY_BATCH_THROUGHPUT_TEST_START_TIME = '15m30s'; export const options = { - setupTimeout: '8m', - teardownTimeout: '6m', + setupTimeout: '30m', + teardownTimeout: '10m', scenarios: { passthrough_read_scenario: { executor: 'constant-vus', diff --git a/k6-tests/setup.sh b/k6-tests/setup.sh index 346b9c0690..c2cdc206be 100755 --- a/k6-tests/setup.sh +++ b/k6-tests/setup.sh @@ -18,7 +18,7 @@ docker-compose -f ../docker-compose/docker-compose.yml --profile dmi-stub up -d echo "Waiting for CPS to start..." -READY_MESSAGE="Processing module sync fetched 0 advised cm handles from DB" +READY_MESSAGE="Inventory Model updated successfully" # Get the container IDs of the cps-and-ncmp replicas CONTAINER_IDS=$(docker ps --filter "name=cps-and-ncmp" --format "{{.ID}}") diff --git a/policy-executor-stub/pom.xml b/policy-executor-stub/pom.xml index 420f565f9b..3618d7816c 100644 --- a/policy-executor-stub/pom.xml +++ b/policy-executor-stub/pom.xml @@ -6,7 +6,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> diff --git a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java index 8f904cc5f2..789201ffce 100644 --- a/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java +++ b/policy-executor-stub/src/main/java/org/onap/cps/policyexecutor/stub/controller/Sleeper.java @@ -24,8 +24,8 @@ import java.util.concurrent.TimeUnit; import org.springframework.stereotype.Service; /** - * This class is a successfull experiment to extract out sleep functionality so the interrupted exception handling can - * be covered with a test (e.g. using spy ion Sleeper) and help to get too 100% code coverage. + * This class is to extract out sleep functionality so the interrupted exception handling can + * be covered with a test (e.g. using spy on Sleeper) and help to get too 100% code coverage. */ @Service public class Sleeper { @@ -32,7 +32,7 @@ <groupId>org.onap.cps</groupId>
<artifactId>cps-aggregator</artifactId>
- <version>3.5.4-SNAPSHOT</version>
+ <version>3.5.5-SNAPSHOT</version>
<packaging>pom</packaging>
<name>cps</name>
diff --git a/releases/3.5.4-container.yaml b/releases/3.5.4-container.yaml new file mode 100644 index 0000000000..7da8f7b56c --- /dev/null +++ b/releases/3.5.4-container.yaml @@ -0,0 +1,8 @@ +distribution_type: container +container_release_tag: 3.5.4 +project: cps +log_dir: cps-maven-docker-stage-master/947/ +ref: 557404338f8867f8253f82526b43313e669550e0 +containers: + - name: 'cps-and-ncmp' + version: '3.5.4-20241017T151242Z' diff --git a/releases/3.5.4.yaml b/releases/3.5.4.yaml new file mode 100644 index 0000000000..fc987985b5 --- /dev/null +++ b/releases/3.5.4.yaml @@ -0,0 +1,4 @@ +distribution_type: maven +log_dir: cps-maven-stage-master/955/ +project: cps +version: 3.5.4 diff --git a/spotbugs/pom.xml b/spotbugs/pom.xml index aaa3d9500d..293612e46b 100644 --- a/spotbugs/pom.xml +++ b/spotbugs/pom.xml @@ -25,7 +25,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.onap.cps</groupId> <artifactId>spotbugs</artifactId> - <version>3.5.4-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <properties> <nexusproxy>https://nexus.onap.org</nexusproxy> diff --git a/version.properties b/version.properties index fa9fe7b6dc..10de88bf6b 100644 --- a/version.properties +++ b/version.properties @@ -22,7 +22,7 @@ major=3 minor=5 -patch=4 +patch=5 base_version=${major}.${minor}.${patch} |