diff options
69 files changed, 1239 insertions, 580 deletions
diff --git a/cps-application/pom.xml b/cps-application/pom.xml index c37447306c..7aabb6a0e3 100644 --- a/cps-application/pom.xml +++ b/cps-application/pom.xml @@ -172,6 +172,30 @@ </execution> </executions> </plugin> + <plugin> + <groupId>io.github.git-commit-id</groupId> + <artifactId>git-commit-id-maven-plugin</artifactId> + <executions> + <execution> + <id>get-git-info</id> + <goals> + <goal>revision</goal> + </goals> + <phase>validate</phase> + </execution> + </executions> + <configuration> + <dateFormat>yyyy-MM-dd'T'HH:mm:ss.sss'Z'</dateFormat> + <includeOnlyProperties> + <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty> + <includeOnlyProperty>^git.commit.(id.full|message.short|user.name|user.email)$</includeOnlyProperty> + <includeOnlyProperty>^git.branch</includeOnlyProperty> + </includeOnlyProperties> + <generateGitPropertiesFile>true</generateGitPropertiesFile> + <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename> + <commitIdGenerationMode>full</commitIdGenerationMode> + </configuration> + </plugin> </plugins> </build> <profiles> diff --git a/cps-application/src/main/resources/application.yml b/cps-application/src/main/resources/application.yml index 3789e92b08..d7e39f7fae 100644 --- a/cps-application/src/main/resources/application.yml +++ b/cps-application/src/main/resources/application.yml @@ -180,6 +180,11 @@ management: probes: enabled: true + info: + git: + enabled: true + mode: full + logging: format: json level: 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-dependencies/pom.xml b/cps-dependencies/pom.xml index ab145370af..a95e5f42b0 100644 --- a/cps-dependencies/pom.xml +++ b/cps-dependencies/pom.xml @@ -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-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/pom.xml index 1fcea8f567..aa7e1fb13a 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 @@ -63,11 +63,6 @@ <artifactId>spock-spring</artifactId> <scope>test</scope> </dependency> - <dependency> - <groupId>org.spockframework</groupId> - <artifactId>spock-core</artifactId> - <scope>test</scope> - </dependency> </dependencies> </project>
\ No newline at end of file diff --git a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java index 183698c4f5..166488793d 100644 --- a/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java +++ b/cps-ncmp-rest-stub/cps-ncmp-rest-stub-service/src/main/java/org/onap/cps/ncmp/rest/stub/controller/NetworkCmProxyStubController.java @@ -213,7 +213,8 @@ public class NetworkCmProxyStubController implements NetworkCmProxyApi { } @Override - public ResponseEntity<List<String>> searchCmHandleIds(@Valid final CmHandleQueryParameters body) { + public ResponseEntity<List<String>> searchCmHandleIds(@Valid final CmHandleQueryParameters body, + @Valid final Boolean outputAlternateId) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/cps-ncmp-rest/docs/openapi/components.yaml b/cps-ncmp-rest/docs/openapi/components.yaml index 112dddf61c..99072c43e5 100644 --- a/cps-ncmp-rest/docs/openapi/components.yaml +++ b/cps-ncmp-rest/docs/openapi/components.yaml @@ -513,6 +513,14 @@ components: schema: type: string example: my-cm-handle-reference + outputAlternateIdOptionInQuery: + name: outputAlternateId + in: query + description: Boolean parameter to determine if returned value(s) will be cmHandle Ids or Alternate Ids for a given query + required: false + schema: + type: boolean + default: false moduleNameInQuery: name: module-name in: query diff --git a/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml b/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml index ea020f9e81..24b83cf368 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp-inventory.yml @@ -1,6 +1,6 @@ # ============LICENSE_START======================================================= # Copyright (C) 2021 Bell Canada -# Modifications Copyright (C) 2021-2022 Nordix Foundation +# Modifications Copyright (C) 2021-2024 Nordix Foundation # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -121,11 +121,13 @@ getAllCmHandleIdsForRegisteredDmi: searchCmHandleIds: post: - description: Query and get CMHandleIds for additional properties, public properties and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin). + description: Query and get CMHandle references for additional properties, public properties and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin). tags: - network-cm-proxy-inventory summary: Query for CM Handle IDs operationId: searchCmHandleIds + parameters: + - $ref: 'components.yaml#/components/parameters/outputAlternateIdOptionInQuery' requestBody: required: true content: diff --git a/cps-ncmp-rest/docs/openapi/ncmp.yml b/cps-ncmp-rest/docs/openapi/ncmp.yml index adb2419c8a..a3ddc3fb3b 100755 --- a/cps-ncmp-rest/docs/openapi/ncmp.yml +++ b/cps-ncmp-rest/docs/openapi/ncmp.yml @@ -417,11 +417,13 @@ getCmHandleStateById: searchCmHandleIds: post: - description: Execute cm handle query search and return a list of cm handle ids. Any number of conditions can be applied. To be included in the result a cm-handle must fulfill ALL the conditions. An empty collection will be returned in the case that the cm handle does not match a condition. For more on cm handle query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible to query on any data related to the cm handle. For more on CPS Path please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS Path Read the Docs</a>. The cm handle ancestor is automatically returned for this query. + description: Execute cm handle query search and return a list of cm handle references. Any number of conditions can be applied. To be included in the result a cm-handle must fulfill ALL the conditions. An empty collection will be returned in the case that the cm handle does not match a condition. For more on cm handle query search please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/ncmp-cmhandle-querying.html">cm handle query search Read the Docs</a>.<br/>By supplying a CPS Path it is possible to query on any data related to the cm handle. For more on CPS Path please refer to <a href="https://docs.onap.org/projects/onap-cps/en/latest/cps-path.html">CPS Path Read the Docs</a>. The cm handle ancestor is automatically returned for this query. tags: - network-cm-proxy summary: Execute cm handle query upon a given set of query parameters operationId: searchCmHandleIds + parameters: + - $ref: 'components.yaml#/components/parameters/outputAlternateIdOptionInQuery' requestBody: required: true content: diff --git a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java index 42f709dcf5..3676bb1393 100755 --- a/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java +++ b/cps-ncmp-rest/src/main/java/org/onap/cps/ncmp/rest/controller/NetworkCmProxyController.java @@ -268,18 +268,20 @@ public class NetworkCmProxyController implements NetworkCmProxyApi { } /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * - * @param cmHandleQueryParameters the cm handle query parameters - * @return collection of cm handle ids + * @param cmHandleQueryParameters the cm handle query parameters + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) + * @return collection of cm handle ids */ @Override - public ResponseEntity<List<String>> searchCmHandleIds( - final CmHandleQueryParameters cmHandleQueryParameters) { + public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters, + final Boolean outputAlternateId) { final CmHandleQueryApiParameters cmHandleQueryApiParameters = jsonObjectMapper.convertToValueType(cmHandleQueryParameters, CmHandleQueryApiParameters.class); final Collection<String> cmHandleIds - = networkCmProxyInventoryFacade.executeCmHandleIdSearch(cmHandleQueryApiParameters); + = networkCmProxyInventoryFacade.executeCmHandleIdSearch(cmHandleQueryApiParameters, outputAlternateId); return ResponseEntity.ok(List.copyOf(cmHandleIds)); } 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 8aa86ade36..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 @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2022-2023 Nordix Foundation + * Modifications Copyright (C) 2022-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -51,13 +51,22 @@ public class NetworkCmProxyInventoryController implements NetworkCmProxyInventor private final NetworkCmProxyInventoryFacade networkCmProxyInventoryFacade; private final NcmpRestInputMapper ncmpRestInputMapper; + /** + * Get all cm handle references under a registered DMI plugin. + * + * @param cmHandleQueryParameters DMI plugin identifier + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (False) or alternate id (True) + * @return list of cm handle IDs + */ @Override - public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters) { + public ResponseEntity<List<String>> searchCmHandleIds(final CmHandleQueryParameters cmHandleQueryParameters, + final Boolean outputAlternateId) { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = ncmpRestInputMapper .toCmHandleQueryServiceParameters(cmHandleQueryParameters); final Collection<String> cmHandleIds = networkCmProxyInventoryFacade - .executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters); + .executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, outputAlternateId); return ResponseEntity.ok(List.copyOf(cmHandleIds)); } @@ -85,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/NetworkCmProxyControllerSpec.groovy b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy index 43403fa890..5340c6d93f 100644 --- a/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy +++ b/cps-ncmp-rest/src/test/groovy/org/onap/cps/ncmp/rest/controller/NetworkCmProxyControllerSpec.groovy @@ -363,7 +363,7 @@ class NetworkCmProxyControllerSpec extends Specification { given: 'an endpoint and json data' def searchesEndpoint = "$ncmpBasePathV1/ch/id-searches" and: 'the service method is invoked with module names and returns cm handle ids' - 1 * mockNetworkCmProxyInventoryFacade.executeCmHandleIdSearch(_) >> ['ch-1', 'ch-2'] + 1 * mockNetworkCmProxyInventoryFacade.executeCmHandleIdSearch(_, _) >> ['ch-1', 'ch-2'] when: 'the searches api is invoked' def response = mvc.perform(post(searchesEndpoint).contentType(MediaType.APPLICATION_JSON).content('{}')).andReturn().response then: 'cm handle ids are returned' 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 97c68f08f3..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 @@ -1,7 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2021-2022 Bell Canada - * Modifications Copyright (C) 2021-2023 Nordix Foundation + * Modifications Copyright (C) 2021-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,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' @@ -113,7 +113,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { and: 'the mapper service returns a converted object' ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters and: 'the service returns the desired results' - mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> serviceMockResponse + mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, _) >> serviceMockResponse when: 'post request is performed & search is called with the given request parameters' def response = mvc.perform( post("$ncmpBasePathV1/ch/searches") @@ -136,7 +136,7 @@ class NetworkCmProxyInventoryControllerSpec extends Specification { and: 'the mapper service returns a converted object' ncmpRestInputMapper.toCmHandleQueryServiceParameters(_) >> cmHandleQueryServiceParameters and: 'the service returns the desired results' - mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) >> serviceMockResponse + mockNetworkCmProxyInventoryFacade.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, _) >> serviceMockResponse when: 'post request is performed & search is called with the given request parameters' def response = mvc.perform( post("$ncmpBasePathV1/ch/searches") @@ -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 9f7bb08a07..fda4221f6f 100644 --- a/cps-ncmp-service/pom.xml +++ b/cps-ncmp-service/pom.xml @@ -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 1fa801c3c5..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 @@ -29,7 +29,6 @@ import static org.onap.cps.ncmp.impl.inventory.CmHandleQueryParametersValidator. import java.util.Collection; import java.util.Map; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryApiParameters; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.CompositeState; @@ -51,7 +50,6 @@ import org.onap.cps.spi.model.ModuleReference; import org.onap.cps.utils.JsonObjectMapper; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class NetworkCmProxyInventoryFacade { @@ -64,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); } /** @@ -89,12 +88,16 @@ public class NetworkCmProxyInventoryFacade { * Get all cm handle IDs by various properties. * * @param cmHandleQueryServiceParameters cm handle query parameters - * @return collection of cm handle IDs + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false) or alternate id (true) + * @return collection of cm handle IDs */ public Collection<String> executeParameterizedCmHandleIdSearch( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { validateCmHandleQueryParameters(cmHandleQueryServiceParameters, InventoryQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters); + + return parameterizedCmHandleQueryService.queryCmHandleIdsForInventory(cmHandleQueryServiceParameters, + outputAlternateId); } @@ -156,13 +159,16 @@ public class NetworkCmProxyInventoryFacade { * Retrieve cm handle ids for the given query parameters. * * @param cmHandleQueryApiParameters cm handle query parameters + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return cm handle ids */ - public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters) { + public Collection<String> executeCmHandleIdSearch(final CmHandleQueryApiParameters cmHandleQueryApiParameters, + final Boolean outputAlternateId) { final CmHandleQueryServiceParameters cmHandleQueryServiceParameters = jsonObjectMapper.convertToValueType( cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class); validateCmHandleQueryParameters(cmHandleQueryServiceParameters, CmHandleQueryConditions.ALL_CONDITION_NAMES); - return parameterizedCmHandleQueryService.queryCmHandleIds(cmHandleQueryServiceParameters); + return parameterizedCmHandleQueryService.queryCmHandleReferenceIds(cmHandleQueryServiceParameters, + outputAlternateId); } /** diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java index 5343a2e131..b9b295a8a0 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacade.java @@ -28,16 +28,13 @@ import static org.onap.cps.ncmp.api.data.models.DatastoreType.OPERATIONAL; import java.util.Collection; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.data.models.CmResourceAddress; import org.onap.cps.ncmp.api.data.models.DataOperationRequest; import org.onap.cps.ncmp.api.data.models.DatastoreType; import org.onap.cps.ncmp.api.data.models.OperationType; -import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher; import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; -@Slf4j @Service @RequiredArgsConstructor public class NetworkCmProxyFacade { @@ -45,7 +42,6 @@ public class NetworkCmProxyFacade { private final NcmpCachedResourceRequestHandler ncmpCachedResourceRequestHandler; private final NcmpPassthroughResourceRequestHandler ncmpPassthroughResourceRequestHandler; private final DmiDataOperations dmiDataOperations; - private final AlternateIdMatcher alternateIdMatcher; /** * Fetches resource data for a given data store using DMI (Data Management Interface). diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java index 95c3c77b71..86e9c934ce 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryService.java @@ -32,25 +32,31 @@ public interface CmHandleQueryService { * Query Cm Handles based on additional (private) properties. * * @param additionalPropertyQueryPairs private properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return Ids of Cm Handles which have these private properties */ - Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs); + Collection<String> queryCmHandleAdditionalProperties(Map<String, String> additionalPropertyQueryPairs, + Boolean outputAlternateId); /** * Query Cm Handles based on public properties. * * @param publicPropertyQueryPairs public properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return CmHandles which have these public properties */ - Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs); + Collection<String> queryCmHandlePublicProperties(Map<String, String> publicPropertyQueryPairs, + Boolean outputAlternateId); /** * Query Cm Handles based on Trust Level. * * @param trustLevelPropertyQueryPairs trust level properties for query + * @param outputAlternateId boolean for cm handle reference type either cmHandleId (false) or AlternateId (true) * @return Ids of Cm Handles which have desired trust level */ - Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs); + Collection<String> queryCmHandlesByTrustLevel(Map<String, String> trustLevelPropertyQueryPairs, + Boolean outputAlternateId); /** * Method which returns cm handles by the cm handles state. @@ -101,4 +107,12 @@ public interface CmHandleQueryService { */ Collection<String> getCmHandleIdsByDmiPluginIdentifier(String dmiPluginIdentifier); + /** + * Get map of cmHandle ids and alternate ids by DMI plugin identifier. + * + * @param dmiPluginIdentifier DMI plugin identifier + * @return map of cmHandle references key:CmHandleId Value:AlternateId + */ + Map<String, String> getCmHandleReferencesByDmiPluginIdentifier(String dmiPluginIdentifier); + } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java index 71e7384208..4249b452c3 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImpl.java @@ -29,6 +29,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.stream.Collectors; @@ -53,6 +54,7 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private static final String DESCENDANT_PATH = "//"; private static final String ANCESTOR_CM_HANDLES = "/ancestor::cm-handles"; + private static final String ALTERNATE_ID = "alternate-id"; private final CpsDataService cpsDataService; private final CpsQueryService cpsQueryService; @@ -65,21 +67,23 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { private final CpsValidator cpsValidator; @Override - public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs) { - return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL); + public Collection<String> queryCmHandleAdditionalProperties(final Map<String, String> privatePropertyQueryPairs, + final Boolean outputAlternateId) { + return queryCmHandleAnyProperties(privatePropertyQueryPairs, PropertyType.ADDITIONAL, outputAlternateId); } @Override - public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs) { - return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC); + public Collection<String> queryCmHandlePublicProperties(final Map<String, String> publicPropertyQueryPairs, + final Boolean outputAlternateId) { + return queryCmHandleAnyProperties(publicPropertyQueryPairs, PropertyType.PUBLIC, outputAlternateId); } @Override - public Collection<String> queryCmHandlesByTrustLevel(final Map<String, String> trustLevelPropertyQueryPairs) { + public Collection<String> queryCmHandlesByTrustLevel(final Map<String, String> trustLevelPropertyQueryPairs, + final Boolean outputAlternateId) { final String trustLevelProperty = trustLevelPropertyQueryPairs.values().iterator().next(); final TrustLevel targetTrustLevel = TrustLevel.valueOf(trustLevelProperty); - - return getCmHandleIdsByTrustLevel(targetTrustLevel); + return getCmHandleReferencesByTrustLevel(targetTrustLevel, outputAlternateId); } @Override @@ -130,37 +134,64 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { return cmHandleIds; } - private Collection<String> getCmHandleIdsByTrustLevel(final TrustLevel targetTrustLevel) { - final Collection<String> selectedCmHandleIds = new HashSet<>(); + @Override + public Map<String, String> getCmHandleReferencesByDmiPluginIdentifier(final String dmiPluginIdentifier) { + final Map<String, String> cmHandleReferencesMap = new HashMap<>(); + for (final ModelledDmiServiceLeaves modelledDmiServiceLeaf : ModelledDmiServiceLeaves.values()) { + for (final DataNode cmHandleAsDataNode: getCmHandlesByDmiPluginIdentifierAndDmiProperty( + dmiPluginIdentifier, + modelledDmiServiceLeaf.getLeafName())) { + cmHandleReferencesMap.put(cmHandleAsDataNode.getLeaves().get("id").toString(), + cmHandleAsDataNode.getLeaves().get(ALTERNATE_ID).toString()); + } + } + return cmHandleReferencesMap; + } + + private Collection<String> getCmHandleReferencesByTrustLevel(final TrustLevel targetTrustLevel, + final Boolean outputAlternateId) { + final Collection<String> selectedCmHandleReferences = new HashSet<>(); for (final Map.Entry<String, TrustLevel> mapEntry : trustLevelPerDmiPlugin.entrySet()) { final String dmiPluginIdentifier = mapEntry.getKey(); final TrustLevel dmiTrustLevel = mapEntry.getValue(); - final Collection<String> candidateCmHandleIds = getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifier); - for (final String candidateCmHandleId : candidateCmHandleIds) { - final TrustLevel candidateCmHandleTrustLevel = trustLevelPerCmHandle.get(candidateCmHandleId); + final Map<String, String> candidateCmHandleReferences = + getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifier); + for (final Map.Entry<String, String> candidateCmHandleReference : candidateCmHandleReferences.entrySet()) { + final TrustLevel candidateCmHandleTrustLevel = + trustLevelPerCmHandle.get(candidateCmHandleReference.getKey()); final TrustLevel effectiveTrustlevel = candidateCmHandleTrustLevel.getEffectiveTrustLevel(dmiTrustLevel); if (targetTrustLevel.equals(effectiveTrustlevel)) { - selectedCmHandleIds.add(candidateCmHandleId); + if (Boolean.TRUE.equals(outputAlternateId)) { + selectedCmHandleReferences.add(candidateCmHandleReference.getValue()); + } else { + selectedCmHandleReferences.add(candidateCmHandleReference.getKey()); + } } } } - - return selectedCmHandleIds; + return selectedCmHandleReferences; } - private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) { - return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + private Collection<String> collectCmHandleReferencesFromDataNodes(final Collection<DataNode> dataNodes, + final Boolean outputAlternateId) { + if (Boolean.TRUE.equals(outputAlternateId)) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get(ALTERNATE_ID)).collect(Collectors.toSet()); + } else { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + } } private Collection<String> queryCmHandleAnyProperties( final Map<String, String> propertyQueryPairs, - final PropertyType propertyType) { + final PropertyType propertyType, final Boolean outputAlternateId) { if (propertyQueryPairs.isEmpty()) { return Collections.emptySet(); } - Collection<String> cmHandleIds = null; + Collection<String> cmHandleReferences = null; for (final Map.Entry<String, String> publicPropertyQueryPair : propertyQueryPairs.entrySet()) { final String cpsPath = DESCENDANT_PATH + propertyType.getYangContainerName() + "[@name=\"" + publicPropertyQueryPair.getKey() @@ -168,17 +199,18 @@ public class CmHandleQueryServiceImpl implements CmHandleQueryService { final Collection<DataNode> dataNodes = queryCmHandleAncestorsByCpsPath(cpsPath, OMIT_DESCENDANTS); - if (cmHandleIds == null) { - cmHandleIds = collectCmHandleIdsFromDataNodes(dataNodes); + if (cmHandleReferences == null) { + cmHandleReferences = collectCmHandleReferencesFromDataNodes(dataNodes, outputAlternateId); } else { - final Collection<String> cmHandleIdsToRetain = collectCmHandleIdsFromDataNodes(dataNodes); - cmHandleIds.retainAll(cmHandleIdsToRetain); + final Collection<String> cmHandleReferencesToRetain; + cmHandleReferencesToRetain = collectCmHandleReferencesFromDataNodes(dataNodes, outputAlternateId); + cmHandleReferences.retainAll(cmHandleReferencesToRetain); } - if (cmHandleIds.isEmpty()) { + if (cmHandleReferences.isEmpty()) { break; } } - return cmHandleIds; + return cmHandleReferences; } private Collection<DataNode> getCmHandlesByDmiPluginIdentifierAndDmiProperty(final String dmiPluginIdentifier, 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/InventoryPersistence.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java index a0d3a3eaee..850edf7d57 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistence.java @@ -149,9 +149,12 @@ public interface InventoryPersistence extends NcmpPersistence { * get CM handles that has given module names. * * @param moduleNamesForQuery module names + * @param outputAlternateIds Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) * @return Collection of CM handle Ids */ - Collection<String> getCmHandleIdsWithGivenModules(Collection<String> moduleNamesForQuery); + Collection<String> getCmHandleReferencesWithGivenModules(Collection<String> moduleNamesForQuery, + Boolean outputAlternateIds); /** * Check database if cm handle id exists if not return false. @@ -159,5 +162,5 @@ public interface InventoryPersistence extends NcmpPersistence { * @param cmHandleId cmHandle Id * @return Boolean */ - boolean isExistingCmHandleId(String cmHandleId); + Boolean isExistingCmHandleId(String cmHandleId); } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java index 06c3f8d2f4..655d8437b1 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImpl.java @@ -201,12 +201,19 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv } @Override - public Collection<String> getCmHandleIdsWithGivenModules(final Collection<String> moduleNamesForQuery) { - return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + public Collection<String> getCmHandleReferencesWithGivenModules(final Collection<String> moduleNamesForQuery, + final Boolean outputAlternateIds) { + if (Boolean.TRUE.equals(outputAlternateIds)) { + final Collection<String> cmHandleIds = + cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + return getAlternateIdsFromDataNodes(getCmHandleDataNodes(cmHandleIds)); + } else { + return cpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, moduleNamesForQuery); + } } @Override - public boolean isExistingCmHandleId(final String cmHandleId) { + public Boolean isExistingCmHandleId(final String cmHandleId) { try { return !getCmHandleDataNodeByCmHandleId(cmHandleId).isEmpty(); } catch (final DataNodeNotFoundException exception) { @@ -234,4 +241,9 @@ public class InventoryPersistenceImpl extends NcmpPersistenceImpl implements Inv private String createCmHandlesJsonData(final List<YangModelCmHandle> yangModelCmHandles) { return "{\"cm-handles\":" + jsonObjectMapper.asJsonString(yangModelCmHandles) + "}"; } + + private Collection<String> getAlternateIdsFromDataNodes(final Collection<DataNode> dataNodes) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("alternate-id")).collect(Collectors.toSet()); + } } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java index e5848c0dfa..8301579b42 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryService.java @@ -26,19 +26,22 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; public interface ParameterizedCmHandleQueryService { /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * Supported query types: * public properties * modules * cps-path * * @param cmHandleQueryServiceParameters the cm handle query parameters - * @return collection of cm handle ids + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) + * @return collection of cm handle ids or alternate ids */ - Collection<String> queryCmHandleIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Collection<String> queryCmHandleReferenceIds(CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + Boolean outputAlternateId); /** - * Query and return cm handle ids that match the given query parameters. + * Query and return cm handle ids or alternate ids that match the given query parameters. * Supported query types: * public properties * private (additional) properties @@ -46,9 +49,12 @@ public interface ParameterizedCmHandleQueryService { * The inventory interface also allows conditions on private (additional) properties and dmi names * * @param cmHandleQueryServiceParameters the cm handle query parameters + * @param outputAlternateId Boolean for cm handle reference type either + * cm handle id (false or null) or alternate id (true) * @return collection of cm handle ids */ - Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters); + Collection<String> queryCmHandleIdsForInventory(CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + Boolean outputAlternateId); /** * Query and return cm handle objects that match the given query parameters. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java index 34eeaccf8f..bacbbe0c95 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceImpl.java @@ -37,10 +37,9 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.onap.cps.cpspath.parser.PathParsingException; import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters; import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; @@ -54,7 +53,6 @@ import org.onap.cps.spi.model.DataNode; import org.springframework.stereotype.Service; @Service -@Slf4j @RequiredArgsConstructor public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHandleQueryService { @@ -63,19 +61,21 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan private final InventoryPersistence inventoryPersistence; @Override - public Collection<String> queryCmHandleIds( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - return executeQueries(cmHandleQueryServiceParameters, - this::executeCpsPathQuery, - this::queryCmHandlesByPublicProperties, - this::executeModuleNameQuery, - this::queryCmHandlesByTrustLevel); + public Collection<String> queryCmHandleReferenceIds( + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final Boolean outputAlternateId) { + return executeQueries(cmHandleQueryServiceParameters, outputAlternateId, + this::executeCpsPathQuery, + this::queryCmHandlesByPublicProperties, + this::executeModuleNameQuery, + this::queryCmHandlesByTrustLevel); } @Override public Collection<String> queryCmHandleIdsForInventory( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { - return executeQueries(cmHandleQueryServiceParameters, + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, + final Boolean outputAlternateId) { + return executeQueries(cmHandleQueryServiceParameters, outputAlternateId, this::executeCpsPathQuery, this::queryCmHandlesByPublicProperties, this::queryCmHandlesByPrivateProperties, @@ -90,7 +90,7 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return getAllCmHandles(); } - final Collection<String> cmHandleIds = queryCmHandleIds(cmHandleQueryServiceParameters); + final Collection<String> cmHandleIds = queryCmHandleReferenceIds(cmHandleQueryServiceParameters, false); return getNcmpServiceCmHandles(cmHandleIds); } @@ -102,7 +102,7 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } private Collection<String> queryCmHandlesByDmiPlugin( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> dmiPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), InventoryQueryConditions.CM_HANDLE_WITH_DMI_PLUGIN.getName()); @@ -113,11 +113,15 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan final String dmiPluginIdentifierValue = dmiPropertyQueryPairs .get(PropertyType.DMI_PLUGIN.getYangContainerName()); - return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); + if (Boolean.TRUE.equals(outputAlternateId)) { + return cmHandleQueryService.getCmHandleReferencesByDmiPluginIdentifier(dmiPluginIdentifierValue).values(); + } else { + return cmHandleQueryService.getCmHandleIdsByDmiPluginIdentifier(dmiPluginIdentifierValue); + } } private Collection<String> queryCmHandlesByPrivateProperties( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> privatePropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -126,11 +130,11 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (privatePropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs); + return cmHandleQueryService.queryCmHandleAdditionalProperties(privatePropertyQueryPairs, outputAlternateId); } private Collection<String> queryCmHandlesByPublicProperties( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> publicPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -139,11 +143,12 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (publicPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlePublicProperties(publicPropertyQueryPairs, outputAlternateId); } private Collection<String> queryCmHandlesByTrustLevel(final CmHandleQueryServiceParameters - cmHandleQueryServiceParameters) { + cmHandleQueryServiceParameters, + final Boolean outputAlternateId) { final Map<String, String> trustLevelPropertyQueryPairs = getPropertyPairs(cmHandleQueryServiceParameters.getCmHandleQueryParameters(), @@ -152,21 +157,21 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan if (trustLevelPropertyQueryPairs.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs); + return cmHandleQueryService.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs, outputAlternateId); } private Collection<String> executeModuleNameQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Collection<String> moduleNamesForQuery = getModuleNamesForQuery(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (moduleNamesForQuery.isEmpty()) { return NO_QUERY_TO_EXECUTE; } - return inventoryPersistence.getCmHandleIdsWithGivenModules(moduleNamesForQuery); + return inventoryPersistence.getCmHandleReferencesWithGivenModules(moduleNamesForQuery, outputAlternateId); } private Collection<String> executeCpsPathQuery( - final CmHandleQueryServiceParameters cmHandleQueryServiceParameters) { + final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, final Boolean outputAlternateId) { final Map<String, String> cpsPathCondition = getCpsPathCondition(cmHandleQueryServiceParameters.getCmHandleQueryParameters()); if (!validateCpsPathConditionProperties(cpsPathCondition)) { @@ -177,9 +182,9 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return NO_QUERY_TO_EXECUTE; } try { - cpsPathQueryResult = collectCmHandleIdsFromDataNodes( - cmHandleQueryService.queryCmHandleAncestorsByCpsPath( - cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS)); + cpsPathQueryResult = collectCmHandleReferencesFromDataNodes( + cmHandleQueryService.queryCmHandleAncestorsByCpsPath(cpsPathCondition.get("cpsPath"), OMIT_DESCENDANTS), + outputAlternateId); } catch (final PathParsingException pathParsingException) { throw new DataValidationException(pathParsingException.getMessage(), pathParsingException.getDetails(), pathParsingException); @@ -220,10 +225,10 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan return Collections.emptyList(); } - private Collection<String> getAllCmHandleIds() { + private Collection<String> getAllCmHandleReferences(final Boolean outputAlternateId) { final DataNode dataNode = inventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, DIRECT_CHILDREN_ONLY) - .iterator().next(); - return collectCmHandleIdsFromDataNodes(dataNode.getChildDataNodes()); + .iterator().next(); + return collectCmHandleReferencesFromDataNodes(dataNode.getChildDataNodes(), outputAlternateId); } private Collection<NcmpServiceCmHandle> getNcmpServiceCmHandles(final Collection<String> cmHandleIds) { @@ -243,14 +248,17 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } private Collection<String> executeQueries(final CmHandleQueryServiceParameters cmHandleQueryServiceParameters, - final Function<CmHandleQueryServiceParameters, Collection<String>>... - queryFunctions) { + final Boolean outputAlternateId, + final BiFunction<CmHandleQueryServiceParameters, Boolean, + Collection<String>>... queryFunctions) { if (cmHandleQueryServiceParameters.getCmHandleQueryParameters().isEmpty()) { - return getAllCmHandleIds(); + return getAllCmHandleReferences(outputAlternateId); } Collection<String> combinedQueryResult = NO_QUERY_TO_EXECUTE; - for (final Function<CmHandleQueryServiceParameters, Collection<String>> queryFunction : queryFunctions) { - final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters); + for (final BiFunction<CmHandleQueryServiceParameters, Boolean, + Collection<String>> queryFunction : queryFunctions) { + final Collection<String> queryResult = queryFunction.apply(cmHandleQueryServiceParameters, + outputAlternateId); if (noEntriesFoundCanStopQuerying(queryResult)) { return Collections.emptySet(); } @@ -277,8 +285,14 @@ public class ParameterizedCmHandleQueryServiceImpl implements ParameterizedCmHan } } - private Collection<String> collectCmHandleIdsFromDataNodes(final Collection<DataNode> dataNodes) { - return dataNodes.stream().map(dataNode -> (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + private Collection<String> collectCmHandleReferencesFromDataNodes(final Collection<DataNode> dataNodes, + final Boolean outputAlternateId) { + if (Boolean.TRUE.equals(outputAlternateId)) { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("alternate-id")).collect(Collectors.toSet()); + } else { + return dataNodes.stream().map(dataNode -> + (String) dataNode.getLeaves().get("id")).collect(Collectors.toSet()); + } } - } diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java index b8bb64f537..80bc4ab69f 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/AsyncTaskExecutor.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * 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. @@ -68,9 +68,9 @@ public class AsyncTaskExecutor { private void handleTaskCompletion(final Object response, final Throwable throwable) { if (throwable != null) { if (throwable instanceof TimeoutException) { - log.warn("Async task didn't completed within the required time."); + log.error("Async task didn't complete within the required time.", throwable); } else { - log.debug("Watchdog async batch failed. caused by : {}", throwable.getMessage()); + log.error("Watchdog async batch failed.", throwable); } } } 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 e627f8f894..31fcbad08b 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 @@ -58,37 +58,16 @@ public class ModuleSyncTasks { */ public CompletableFuture<Void> performModuleSync(final Collection<DataNode> cmHandlesAsDataNodes, final AtomicInteger batchCounter) { + final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle = + new HashMap<>(cmHandlesAsDataNodes.size()); try { - final Map<YangModelCmHandle, CmHandleState> cmHandelStatePerCmHandle - = new HashMap<>(cmHandlesAsDataNodes.size()); - for (final DataNode cmHandleAsDataNode : cmHandlesAsDataNodes) { - final String cmHandleId = String.valueOf(cmHandleAsDataNode.getLeaves().get("id")); + cmHandlesAsDataNodes.forEach(cmHandleAsDataNode -> { final YangModelCmHandle yangModelCmHandle = YangDataConverter.toYangModelCmHandle(cmHandleAsDataNode); - final CompositeState compositeState = inventoryPersistence.getCmHandleState(cmHandleId); - final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState); - try { - if (inUpgrade) { - moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle); - } else { - moduleSyncService.deleteSchemaSetIfExists(cmHandleId); - moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); - } - yangModelCmHandle.getCompositeState().setLockReason(null); - cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.READY); - } catch (final Exception e) { - log.warn("Processing of {} module failed due to reason {}.", cmHandleId, e.getMessage()); - final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED - : LockReasonCategory.MODULE_SYNC_FAILED; - moduleOperationsUtils.updateLockReasonWithAttempts(compositeState, - lockReasonCategory, e.getMessage()); - setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); - cmHandelStatePerCmHandle.put(yangModelCmHandle, CmHandleState.LOCKED); - } - log.info("{} is now in {} state", cmHandleId, cmHandelStatePerCmHandle.get(yangModelCmHandle).name()); - } - lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandelStatePerCmHandle); + cmHandleStatePerCmHandle.put(yangModelCmHandle, processCmHandle(yangModelCmHandle)); + }); } finally { batchCounter.getAndDecrement(); + lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); log.info("Processing module sync batch finished. {} batch(es) active.", batchCounter.get()); } return CompletableFuture.completedFuture(null); @@ -108,13 +87,36 @@ public class ModuleSyncTasks { 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: {}", - yangModelCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); + yangModelCmHandle.getId(), compositeState.getLockReason().getLockReasonCategory().name()); cmHandleStatePerCmHandle.put(yangModelCmHandle, CmHandleState.ADVISED); removeResetCmHandleFromModuleSyncMap(resetCmHandleId); } lcmEventsCmHandleStateHandler.updateCmHandleStateBatch(cmHandleStatePerCmHandle); } + private CmHandleState processCmHandle(final YangModelCmHandle yangModelCmHandle) { + final CompositeState compositeState = inventoryPersistence.getCmHandleState(yangModelCmHandle.getId()); + final boolean inUpgrade = ModuleOperationsUtils.inUpgradeOrUpgradeFailed(compositeState); + try { + if (inUpgrade) { + moduleSyncService.syncAndUpgradeSchemaSet(yangModelCmHandle); + } else { + moduleSyncService.deleteSchemaSetIfExists(yangModelCmHandle.getId()); + moduleSyncService.syncAndCreateSchemaSetAndAnchor(yangModelCmHandle); + } + yangModelCmHandle.getCompositeState().setLockReason(null); + return CmHandleState.READY; + } catch (final Exception e) { + log.warn("Processing of {} module failed due to reason {}.", yangModelCmHandle.getId(), e.getMessage()); + final LockReasonCategory lockReasonCategory = inUpgrade ? LockReasonCategory.MODULE_UPGRADE_FAILED + : LockReasonCategory.MODULE_SYNC_FAILED; + moduleOperationsUtils.updateLockReasonWithAttempts(compositeState, + lockReasonCategory, e.getMessage()); + setCmHandleStateLocked(yangModelCmHandle, compositeState.getLockReason()); + return CmHandleState.LOCKED; + } + } + private void setCmHandleStateLocked(final YangModelCmHandle advisedCmHandle, final CompositeState.LockReason lockReason) { advisedCmHandle.getCompositeState().setLockReason(lockReason); @@ -125,4 +127,4 @@ public class ModuleSyncTasks { log.info("{} removed from in progress map", resetCmHandleId); } } -} +}
\ No newline at end of file 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 4061298cd0..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 @@ -32,6 +32,7 @@ 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; @@ -47,6 +48,7 @@ public class ModuleSyncWatchdog { 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; @@ -59,9 +61,10 @@ 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.debug("Processing module sync watchdog waking up."); populateWorkQueueIfNeeded(); @@ -82,26 +85,20 @@ public class ModuleSyncWatchdog { } } - private void preventBusyWait() { - try { - log.debug("Busy waiting now"); - TimeUnit.MILLISECONDS.sleep(PREVENT_CPU_BURN_WAIT_TIME_MILLIS); - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - private void populateWorkQueueIfNeeded() { - if (moduleSyncWorkQueue.isEmpty()) { - if (workQueueLock.tryLock()) { - try { - populateWorkQueue(); - if (moduleSyncWorkQueue.isEmpty()) { - setPreviouslyLockedCmHandlesToAdvised(); - } - } finally { - workQueueLock.unlock(); + /** + * 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. + */ + public void populateWorkQueueIfNeeded() { + if (moduleSyncWorkQueue.isEmpty() && workQueueLock.tryLock()) { + try { + populateWorkQueue(); + if (moduleSyncWorkQueue.isEmpty()) { + setPreviouslyLockedCmHandlesToAdvised(); } + } finally { + workQueueLock.unlock(); } } } @@ -156,4 +153,13 @@ public class ModuleSyncWatchdog { 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/lcm/LcmEventsCmHandleStateHandler.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java index 6cce153269..de3df6b9da 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandler.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * 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. @@ -32,14 +32,6 @@ import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; public interface LcmEventsCmHandleStateHandler { /** - * Updates the composite state of cmHandle based on cmHandleState. - * - * @param yangModelCmHandle cm handle represented as yang model - * @param targetCmHandleState target cm handle state - */ - void updateCmHandleState(final YangModelCmHandle yangModelCmHandle, final CmHandleState targetCmHandleState); - - /** * Updates the composite state of cmHandle based on cmHandleState in batch. * * @param cmHandleStatePerCmHandle Map of Yang Model Cm Handle and corresponding cm handle state. diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java index cf7921c350..a53c902683 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerAsyncHelper.java @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.events.lcm.v1.LcmEvent; import org.onap.cps.ncmp.events.lcm.v1.LcmEventHeader; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; +import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair; import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -38,25 +39,12 @@ public class LcmEventsCmHandleStateHandlerAsyncHelper { private final LcmEventsService lcmEventsService; /** - * Publish LCM Event in asynchronous manner. - * - * @param targetNcmpServiceCmHandle target NcmpServiceCmHandle - * @param currentNcmpServiceCmHandle current NcmpServiceCmHandle - */ - @Async("notificationExecutor") - public void publishLcmEventAsynchronously(final NcmpServiceCmHandle targetNcmpServiceCmHandle, - final NcmpServiceCmHandle currentNcmpServiceCmHandle) { - publishLcmEvent(targetNcmpServiceCmHandle, currentNcmpServiceCmHandle); - } - - /** * Publish LcmEvent in batches and in asynchronous manner. * * @param cmHandleTransitionPairs Pair of existing and modified cm handle represented as YangModelCmHandle */ @Async("notificationExecutor") - public void publishLcmEventBatchAsynchronously( - final Collection<LcmEventsCmHandleStateHandlerImpl.CmHandleTransitionPair> cmHandleTransitionPairs) { + public void publishLcmEventBatchAsynchronously(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { cmHandleTransitionPairs.forEach(cmHandleTransitionPair -> publishLcmEvent( toNcmpServiceCmHandle(cmHandleTransitionPair.getTargetYangModelCmHandle()), toNcmpServiceCmHandle(cmHandleTransitionPair.getCurrentYangModelCmHandle()))); diff --git a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java index b1b7b955f7..e9bd37219a 100644 --- a/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java +++ b/cps-ncmp-service/src/main/java/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImpl.java @@ -38,12 +38,10 @@ import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.onap.cps.ncmp.api.inventory.models.CompositeState; -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle; import org.onap.cps.ncmp.impl.inventory.CompositeStateUtils; import org.onap.cps.ncmp.impl.inventory.InventoryPersistence; import org.onap.cps.ncmp.impl.inventory.models.CmHandleState; import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle; -import org.onap.cps.ncmp.impl.utils.YangDataConverter; import org.springframework.stereotype.Service; @Slf4j @@ -55,25 +53,6 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState private final LcmEventsCmHandleStateHandlerAsyncHelper lcmEventsCmHandleStateHandlerAsyncHelper; @Override - public void updateCmHandleState(final YangModelCmHandle updatedYangModelCmHandle, - final CmHandleState targetCmHandleState) { - - final CompositeState compositeState = updatedYangModelCmHandle.getCompositeState(); - - if (isCompositeStateSame(compositeState, targetCmHandleState)) { - log.debug("CmHandle with id : {} already in state : {}", updatedYangModelCmHandle.getId(), - targetCmHandleState); - } else { - final YangModelCmHandle currentYangModelCmHandle = YangModelCmHandle.deepCopyOf(updatedYangModelCmHandle); - updateToSpecifiedCmHandleState(updatedYangModelCmHandle, targetCmHandleState); - persistCmHandle(updatedYangModelCmHandle, currentYangModelCmHandle); - lcmEventsCmHandleStateHandlerAsyncHelper.publishLcmEventAsynchronously( - toNcmpServiceCmHandle(updatedYangModelCmHandle), - toNcmpServiceCmHandle(currentYangModelCmHandle)); - } - } - - @Override @Timed(value = "cps.ncmp.cmhandle.state.update.batch", description = "Time taken to update a batch of cm handle states") public void updateCmHandleStateBatch(final Map<YangModelCmHandle, CmHandleState> cmHandleStatePerCmHandle) { @@ -113,28 +92,13 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState return cmHandleTransitionPairs; } - - private void persistCmHandle(final YangModelCmHandle targetYangModelCmHandle, - final YangModelCmHandle currentYangModelCmHandle) { - if (isNew(currentYangModelCmHandle.getCompositeState())) { - log.debug("Registering a new cm handle {}", targetYangModelCmHandle.getId()); - inventoryPersistence.saveCmHandle(targetYangModelCmHandle); - } else if (isDeleted(targetYangModelCmHandle.getCompositeState())) { - log.info("CmHandle with Id : {} is DELETED", targetYangModelCmHandle.getId()); - } else { - inventoryPersistence.saveCmHandleState(targetYangModelCmHandle.getId(), - targetYangModelCmHandle.getCompositeState()); - } - } - private void persistCmHandleBatch(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { final List<YangModelCmHandle> newCmHandles = new ArrayList<>(); final Map<String, CompositeState> compositeStatePerCmHandleId = new LinkedHashMap<>(); cmHandleTransitionPairs.forEach(cmHandleTransitionPair -> { - if (isNew(cmHandleTransitionPair.getCurrentYangModelCmHandle().getCompositeState() - )) { + if (isNew(cmHandleTransitionPair.getCurrentYangModelCmHandle().getCompositeState())) { newCmHandles.add(cmHandleTransitionPair.getTargetYangModelCmHandle()); } else if (!isDeleted(cmHandleTransitionPair.getTargetYangModelCmHandle().getCompositeState())) { compositeStatePerCmHandleId.put(cmHandleTransitionPair.getTargetYangModelCmHandle().getId(), @@ -145,10 +109,11 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState inventoryPersistence.saveCmHandleBatch(newCmHandles); inventoryPersistence.saveCmHandleStateBatch(compositeStatePerCmHandleId); + logCmHandleStateChanges(cmHandleTransitionPairs); } private void updateToSpecifiedCmHandleState(final YangModelCmHandle yangModelCmHandle, - final CmHandleState targetCmHandleState) { + final CmHandleState targetCmHandleState) { if (READY == targetCmHandleState) { setInitialStates(yangModelCmHandle); @@ -193,8 +158,11 @@ public class LcmEventsCmHandleStateHandlerImpl implements LcmEventsCmHandleState return (compositeState != null && compositeState.getCmHandleState() == targetCmHandleState); } - private NcmpServiceCmHandle toNcmpServiceCmHandle(final YangModelCmHandle yangModelCmHandle) { - return YangDataConverter.toNcmpServiceCmHandle(yangModelCmHandle); + private static void logCmHandleStateChanges(final Collection<CmHandleTransitionPair> cmHandleTransitionPairs) { + cmHandleTransitionPairs.stream() + .map(CmHandleTransitionPair::getTargetYangModelCmHandle) + .forEach(yangModelCmHandle -> log.info("{} is now in {} state", yangModelCmHandle.getId(), + yangModelCmHandle.getCompositeState().getCmHandleState().name())); } @Getter 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/data/NetworkCmProxyFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy index 5f83ad5f83..c62e93c214 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/NetworkCmProxyFacadeSpec.groovy @@ -42,9 +42,8 @@ class NetworkCmProxyFacadeSpec extends Specification { def mockDmiDataOperations = Mock(DmiDataOperations) def mockNcmpCachedResourceRequestHandler = Mock(NcmpCachedResourceRequestHandler) def mockNcmpPassthroughResourceRequestHandler = Mock(NcmpPassthroughResourceRequestHandler) - def mockAlternateIdMatcher = Mock(AlternateIdMatcher) - def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations, mockAlternateIdMatcher) + def objectUnderTest = new NetworkCmProxyFacade(mockNcmpCachedResourceRequestHandler, mockNcmpPassthroughResourceRequestHandler, mockDmiDataOperations) def NO_TOPIC = null @@ -89,7 +88,6 @@ class NetworkCmProxyFacadeSpec extends Specification { given: 'a cm resource address for datastore operational' def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'some CM Handle', 'some resource Id') and: 'get resource data from DMI is called' - mockAlternateIdMatcher.getCmHandleId('some CM Handle') >> 'some CM Handle' mockNcmpCachedResourceRequestHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'authorization') >> Mono.just('dmi response') when: 'get resource data operational for the given cm resource address is called' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index 7e34fe2822..0c50e3d4ee 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -64,16 +64,16 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' mockResponses() when: 'a query on cmhandle public properties is performed with a public property pair' - def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs) + def result = objectUnderTest.queryCmHandlePublicProperties(publicPropertyPairs, outputAlternateId) then: 'the correct cm handle data objects are returned' - result.containsAll(expectedCmHandleIds) - result.size() == expectedCmHandleIds.size() + result.containsAll(expectedCmHandleReferences) + result.size() == expectedCmHandleReferences.size() where: 'the following data is used' - scenario | publicPropertyPairs || expectedCmHandleIds - 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] - 'public property does not match' | [wont_match: 'wont_match'] || [] - '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] || ['PNFDemo4'] - '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] || [] + scenario | publicPropertyPairs | outputAlternateId || expectedCmHandleReferences + 'single property matches' | [Contact: 'newemailforstore@bookstore.com'] | false || ['PNFDemo', 'PNFDemo2', 'PNFDemo4'] + 'public property does not match' | [wont_match: 'wont_match'] | false || [] + '2 properties, only one match' | [Contact: 'newemailforstore@bookstore.com', Contact2: 'newemailforstore2@bookstore.com'] | true || ['alt-PNFDemo4'] + '2 properties, no matches' | [Contact: 'newemailforstore@bookstore.com', Contact2: ''] | false || [] } def 'Query cm handles on trust level'() { @@ -84,22 +84,26 @@ class CmHandleQueryServiceImplSpec extends Specification { and: 'the DataNodes queried for a given cpsPath are returned from the persistence service' mockResponses() when: 'the query is run' - def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs) - then: 'the result contain trusted PNFDemo' + def result = objectUnderTest.queryCmHandlesByTrustLevel(trustLevelPropertyQueryPairs, outputAlternateId) + then: 'the result contain trusted cmHandle reference' assert result.size() == 1 - assert result[0] == 'PNFDemo' + assert result[0] == expectedCmHandleReference + where: 'the following data is used' + senario | outputAlternateId | expectedCmHandleReference + 'output cmHandleId' | false | 'PNFDemo' + 'output AlternateId' | true | 'alt-PNFDemo' } def 'Query CmHandles using empty public properties query pair.'() { when: 'a query on CmHandle public properties is executed using an empty map' - def result = objectUnderTest.queryCmHandlePublicProperties([:]) + def result = objectUnderTest.queryCmHandlePublicProperties([:], false) then: 'no cm handles are returned' result.size() == 0 } def 'Query CmHandles using empty private properties query pair.'() { when: 'a query on CmHandle private properties is executed using an empty map' - def result = objectUnderTest.queryCmHandleAdditionalProperties([:]) + def result = objectUnderTest.queryCmHandleAdditionalProperties([:], false) then: 'no cm handles are returned' result.size() == 0 } @@ -108,7 +112,7 @@ class CmHandleQueryServiceImplSpec extends Specification { given: 'a data node exists with a certain additional-property' mockCpsQueryService.queryDataNodes(_, _, dataNodeWithPrivateField, _) >> [pnfDemo5] when: 'a query on CmHandle private properties is executed using a map' - def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com']) + def result = objectUnderTest.queryCmHandleAdditionalProperties(['Contact3': 'newemailforstore3@bookstore.com'], false) then: 'one cm handle is returned' result.size() == 1 } @@ -206,6 +210,17 @@ class CmHandleQueryServiceImplSpec extends Specification { assert result.containsAll('PNFDemo', 'PNFDemo2', 'PNFDemo4') } + def 'Get all alternateIds by dmi plugin identifier'() { + given: 'the DataNodes queried for a given cpsPath are returned from the persistence service.' + mockResponses() + when: 'cm Handles are fetched for a given dmi plugin identifier' + def result = objectUnderTest.getCmHandleReferencesByDmiPluginIdentifier('my-dmi-plugin-identifier').values() + then: 'result is the correct size' + assert result.size() == 3 + and: 'result contains the correct alternate Ids' + assert result.containsAll('alt-PNFDemo', 'alt-PNFDemo2', 'alt-PNFDemo4') + } + void mockResponses() { mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"Contact\" and @value=\"newemailforstore@bookstore.com\"]/ancestor::cm-handles', _) >> [pnfDemo, pnfDemo2, pnfDemo4] mockCpsQueryService.queryDataNodes(_, _, '//public-properties[@name=\"wont_match\" and @value=\"wont_match\"]/ancestor::cm-handles', _) >> [] @@ -219,6 +234,6 @@ class CmHandleQueryServiceImplSpec extends Specification { } def static createDataNode(dataNodeId) { - return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId]) + return new DataNode(xpath: '/dmi-registry/cm-handles[@id=\'' + dataNodeId + '\']', leaves: ['id':dataNodeId, 'alternate-id':'alt-' + dataNodeId]) } } 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/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index 1830f1331d..e2261f4b7c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -38,6 +38,7 @@ import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper +import org.testcontainers.shaded.com.fasterxml.jackson.databind.introspect.BasicClassIntrospector import spock.lang.Shared import spock.lang.Specification @@ -332,13 +333,25 @@ class InventoryPersistenceImplSpec extends Specification { 0 * mockCmHandleQueries.queryNcmpRegistryByCpsPath(_, _) } - def 'Get CM handles that has given module names'() { + def 'Get CM handle ids for CM Handles that has given module names'() { when: 'the method to get cm handles is called' - objectUnderTest.getCmHandleIdsWithGivenModules(['sample-module-name']) + objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], false) then: 'the admin persistence service method to query anchors is invoked once with the same parameter' 1 * mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) } + def 'Get Alternate Ids for CM Handles that has given module names'() { + given: 'A Collection of data nodes' + def dataNodes = [new DataNode(xpath: "/dmi-registry/cm-handles[@id='ch-1']", leaves: ['id': 'ch-1', 'alternate-id': 'alt-1'])] + when: 'the methods to get dataNodes is called and returns correct values' + mockCpsAnchorService.queryAnchorNames(NFP_OPERATIONAL_DATASTORE_DATASPACE_NAME, ['sample-module-name']) >> ['ch-1'] + mockCpsDataService.getDataNodesForMultipleXpaths(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, ["/dmi-registry/cm-handles[@id='ch-1']"], INCLUDE_ALL_DESCENDANTS) >> dataNodes + and: 'the method returns a result' + def result = objectUnderTest.getCmHandleReferencesWithGivenModules(['sample-module-name'], true) + then: 'the result contains the correct alternate Id' + assert result == ['alt-1'] as HashSet + } + def 'Replace list content'() { when: 'replace list content method is called with xpath and data nodes collection' objectUnderTest.replaceListContent('sample xpath', [new DataNode()]) 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 fec07556eb..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,12 +56,12 @@ 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 id search for inventory'() { + def 'Execute cm handle reference search for inventory'() { given: 'a ConditionApiProperties object' def conditionProperties = new ConditionProperties() conditionProperties.conditionName = 'hasAllProperties' @@ -71,7 +71,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { and: 'the system returns an set of cmHandle ids' mockParameterizedCmHandleQueryService.queryCmHandleIdsForInventory(*_) >> [ 'cmHandle1', 'cmHandle2' ] when: 'executing the search' - def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters) + def result = objectUnderTest.executeParameterizedCmHandleIdSearch(cmHandleQueryServiceParameters, false) then: 'the result returns the correct 2 elements' assert result.size() == 2 assert result.contains('cmHandle1') @@ -191,7 +191,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { 'Cm Handle Reference as alternate-id' | 'some-alternate-id' } - def 'Execute cm handle id search'() { + def 'Execute cm handle reference search'() { given: 'valid CmHandleQueryApiParameters input' def cmHandleQueryApiParameters = new CmHandleQueryApiParameters() def conditionApiProperties = new ConditionApiProperties() @@ -199,11 +199,11 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { conditionApiProperties.conditionParameters = [[moduleName: 'module-name-1']] cmHandleQueryApiParameters.cmHandleQueryParameters = [conditionApiProperties] and: 'query cm handle method return with a data node list' - mockParameterizedCmHandleQueryService.queryCmHandleIds( - spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) + mockParameterizedCmHandleQueryService.queryCmHandleReferenceIds( + spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class), false) >> ['cm-handle-id-1'] when: 'execute cm handle search is called' - def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters) + def result = objectUnderTest.executeCmHandleIdSearch(cmHandleQueryApiParameters, false) then: 'result is the same collection as returned by the CPS Data Service' assert result == ['cm-handle-id-1'] } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 013bace04d..9a81807ef9 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * 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. @@ -50,11 +50,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the query get the cm handle datanodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])] when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set + assert result == expectedCmhandleReference + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReference + 'output CmHandle Ids' | false || ['some-cmhandle-id'] as Set + 'output Alternate Ids' | true || ['some-alternate-id'] as Set } def 'Query cm handle where cps path itself is ancestor axis.'() { @@ -63,11 +67,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/some/cps/path']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the query get the cm handle data nodes excluding all descendants returns a datanode' - cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id'])] + cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> [new DataNode(leaves: ['id':'some-cmhandle-id', 'alternate-id':'some-alternate-id'])] when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleIdsForInventory(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles ids are returned' - assert result == ['some-cmhandle-id'] as Set + assert result == expectedCmhandleReference + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReference + 'outputAlternate is false' | false || ['some-cmhandle-id'] as Set + 'outputAlternate is true' | true || ['some-alternate-id'] as Set } def 'Cm handle ids query with error: #scenario.'() { @@ -78,7 +86,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { and: 'cmHandleQueries throws a path parsing exception' cmHandleQueries.queryCmHandleAncestorsByCpsPath('/some/cps/path', FetchDescendantsOption.OMIT_DESCENDANTS) >> { throw thrownException } when: 'the query is executed for cm handle ids' - objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'a data validation exception is thrown' thrown(expectedException) where: 'the following data is used' @@ -93,7 +101,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('cmHandleWithCpsPath', [['cpsPath' : '/additional-properties']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'empty result is returned' assert result.isEmpty() } @@ -104,9 +112,9 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def conditionProperties = createConditionProperties('hasAllModules', [['moduleName': 'some-module-name']]) cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) when: 'the query is executed for cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, false) then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> cmHandleIdsFromService + 1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> cmHandleIdsFromService and: 'the correct expected cm handles ids are returned' assert result.size() == cmHandleIdsFromService.size() assert result.containsAll(cmHandleIdsFromService) @@ -122,9 +130,9 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def trustLevelConditionProperties = createConditionProperties('cmHandleWithTrustLevel', [['trustLevel': 'COMPLETE'] as Map]) trustLevelQueryParameters.setCmHandleQueryParameters([trustLevelConditionProperties]) when: 'the query is being executed' - objectUnderTest.queryCmHandleIds(trustLevelQueryParameters) + objectUnderTest.queryCmHandleReferenceIds(trustLevelQueryParameters, false) then: 'the query is being delegated to the cm handle query service with correct parameter' - 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map) + 1 * cmHandleQueries.queryCmHandlesByTrustLevel(['trustLevel': 'COMPLETE'] as Map, false) } def 'Query cm handle details with module names when #scenario from query.'() { @@ -135,7 +143,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { when: 'the query is executed for cm handle ids' def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the inventory service is called with the correct module names' - 1 * mockInventoryPersistence.getCmHandleIdsWithGivenModules(['some-module-name']) >> ['ch1'] + 1 * mockInventoryPersistence.getCmHandleReferencesWithGivenModules(['some-module-name'], false) >> ['ch1'] and: 'the inventory service is called with teh correct if and returns a yang model cm handle' 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] @@ -145,15 +153,19 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { assert result[0].dmiProperties == [name:'value'] } - def 'Query cm handle ids when the query is empty.'() { + def 'Query cm handle references when the query is empty.'() { given: 'We use an empty query' def cmHandleQueryParameters = new CmHandleQueryServiceParameters() and: 'the inventory persistence returns the dmi registry datanode with just ids' mockInventoryPersistence.getDataNode(NCMP_DMI_REGISTRY_PARENT, FetchDescendantsOption.DIRECT_CHILDREN_ONLY) >> [dmiRegistry] when: 'the query is executed for both cm handle ids' - def result = objectUnderTest.queryCmHandleIds(cmHandleQueryParameters) + def result = objectUnderTest.queryCmHandleReferenceIds(cmHandleQueryParameters, outputAlternateId) then: 'the correct expected cm handles are returned' - assert result.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + assert result.containsAll(expectedCmhandleReferences) + where: 'the following data is used' + senario | outputAlternateId || expectedCmhandleReferences + 'outputAlternate is false' | false || ['PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4'] + 'outputAlternate is true' | true || ['alt-PNFDemo1', 'alt-PNFDemo2', 'alt-PNFDemo3', 'alt-PNFDemo4'] } def 'Query cm handle details when the query is empty.'() { @@ -177,7 +189,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { partiallyMockedCmHandleQueries.queryCmHandlePublicProperties(*_) >> cmHandlesWithMatchingPublicProperties partiallyMockedCmHandleQueries.queryCmHandleAdditionalProperties(*_) >> cmHandlesWithMatchingPrivateProperties when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, false) then: 'the expected number of results are returned.' assert result.size() == expectedCmHandleIdsSize where: 'the following data is used' @@ -195,14 +207,15 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { cmHandleQueryParameters.setCmHandleQueryParameters([conditionProperties]) and: 'the inventoryPersistence returns different CmHandleIds' partiallyMockedCmHandleQueries.getCmHandleIdsByDmiPluginIdentifier(*_) >> cmHandleQueryResult + partiallyMockedCmHandleQueries.getCmHandleReferencesByDmiPluginIdentifier(*_) >> cmHandleQueryResult when: 'the query executed' - def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters) + def result = objectUnderTestWithPartiallyMockedQueries.queryCmHandleIdsForInventory(cmHandleQueryParameters, outputAlternateId) then: 'the expected number of results are returned.' assert result.size() == expectedCmHandleIdsSize where: 'the following data is used' - scenario | cmHandleQueryResult || expectedCmHandleIdsSize - 'some matches' | ['h1','h2'] || 2 - 'no matches' | [] || 0 + scenario | cmHandleQueryResult | outputAlternateId || expectedCmHandleIdsSize + 'some matches' | ['h1','h2'] | false || 2 + 'no matches' | [:] | true || 0 } def 'Combine two query results where #scenario.'() { @@ -227,7 +240,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def static createDataNodeList(dataNodeIds) { def dataNodes =[] - dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it]) } + dataNodeIds.each{ dataNodes << new DataNode(xpath: "/dmi-registry/cm-handles[@id='${it}']", leaves: ['id':it, 'alternate-id':'alt-' + it]) } return dataNodes } } 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 4d715d28c9..8ce1e934f2 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 @@ -34,8 +34,10 @@ import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler +import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.slf4j.LoggerFactory +import spock.lang.Ignore import spock.lang.Specification import java.util.concurrent.atomic.AtomicInteger @@ -121,6 +123,26 @@ class ModuleSyncTasksSpec extends Specification { 'module upgrade' | MODULE_UPGRADE | 'Upgrade in progress' || MODULE_UPGRADE_FAILED } + @Ignore // TODO Enable this test once the bug CPS-2474 is fixed + def 'Module sync succeeds even if a handle gets deleted during module sync.'() { + given: 'cm handles in an ADVISED state' + def cmHandle1 = cmHandleAsDataNodeByIdAndState('cm-handle-1', CmHandleState.ADVISED) + def cmHandle2 = cmHandleAsDataNodeByIdAndState('cm-handle-2', CmHandleState.ADVISED) + and: 'inventory persistence cannot find the first handle' + mockInventoryPersistence.getCmHandleState('cm-handle-1') >> { throw new DataNodeNotFoundException('dataspace', 'anchor', 'xpath') } + and: 'inventory persistence returns the second handle with ADVISED state' + mockInventoryPersistence.getCmHandleState('cm-handle-2') >> new CompositeState(cmHandleState: CmHandleState.ADVISED) + when: 'module sync poll is executed' + objectUnderTest.performModuleSync([cmHandle1, cmHandle2], batchCount) + then: 'no exception is thrown' + noExceptionThrown() + and: 'the deleted cm-handle did not sync' + 0 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-1' } + and: 'the existing cm-handle synced' + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assert args[0].id == 'cm-handle-2' } + and: 'the state handler called' + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) + } def 'Reset failed CM Handles #scenario.'() { given: 'cm handles in an locked state' @@ -153,7 +175,7 @@ class ModuleSyncTasksSpec extends Specification { when: 'module sync poll is executed' objectUnderTest.performModuleSync([cmHandle1], batchCount) then: 'module sync service is invoked for cm handle' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { args -> assertYamgModelCmHandleArgument(args, 'cm-handle-1') } + 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) and: 'the entry for other cm handle is still in the progress map' assert moduleSyncStartedOnCmHandles.get('other-cm-handle') != null } 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 3064a78ff9..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,6 +23,7 @@ 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 @@ -31,7 +32,7 @@ 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 @@ -45,16 +46,21 @@ class ModuleSyncWatchdogSpec extends Specification { def mockWorkQueueLock = Mock(Lock) - def objectUnderTest = new ModuleSyncWatchdog(mockSyncUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock) + def spiedSleeper = Spy(Sleeper) + + def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, spiedAsyncTaskExecutor, mockWorkQueueLock, spiedSleeper) void setup() { spiedAsyncTaskExecutor.setupThreadPool() - mockWorkQueueLock.tryLock() >> true } 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' @@ -63,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 @@ -70,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' @@ -81,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' @@ -98,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' @@ -108,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.setPreviouslyLockedCmHandlesToAdvised() then: 'it is delegated to the module sync task (service)' 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-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy index bd7c321bc7..4b676e1b4c 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/lcm/LcmEventsCmHandleStateHandlerImplSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2023 Nordix Foundation + * 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. @@ -20,12 +20,19 @@ package org.onap.cps.ncmp.impl.inventory.sync.lcm +import ch.qos.logback.classic.Level +import ch.qos.logback.classic.Logger +import ch.qos.logback.classic.spi.ILoggingEvent +import ch.qos.logback.core.read.ListAppender import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.impl.inventory.DataStoreSyncState import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle +import org.slf4j.LoggerFactory import spock.lang.Specification +import static java.util.Collections.EMPTY_LIST +import static java.util.Collections.EMPTY_MAP import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.ADVISED import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETED import static org.onap.cps.ncmp.impl.inventory.models.CmHandleState.DELETING @@ -35,6 +42,17 @@ import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { + def logger = Spy(ListAppender<ILoggingEvent>) + + void setup() { + ((Logger) LoggerFactory.getLogger(LcmEventsCmHandleStateHandlerImpl.class)).addAppender(logger) + logger.start() + } + + void cleanup() { + ((Logger) LoggerFactory.getLogger(LcmEventsCmHandleStateHandlerImpl.class)).detachAndStopAllAppenders() + } + def mockInventoryPersistence = Mock(InventoryPersistence) def mockLcmEventsCreator = Mock(LcmEventsCreator) def mockLcmEventsService = Mock(LcmEventsService) @@ -51,30 +69,39 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { compositeState = new CompositeState(cmHandleState: fromCmHandleState) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, toCmHandleState) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, toCmHandleState)) then: 'state is saved using inventory persistence' - expectedCallsToInventoryPersistence * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { + args -> { + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).cmHandleState == toCmHandleState + } + } + and: 'log message shows state change at INFO level' + def loggingEvent = (ILoggingEvent) logger.list[0] + assert loggingEvent.level == Level.INFO + assert loggingEvent.formattedMessage == "${cmHandleId} is now in ${toCmHandleState} state" and: 'event service is called to publish event' - expectedCallsToEventService * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) where: 'state change parameters are provided' - stateChange | fromCmHandleState | toCmHandleState || expectedCallsToInventoryPersistence | expectedCallsToEventService - 'ADVISED to READY' | ADVISED | READY || 1 | 1 - 'READY to LOCKED' | READY | LOCKED || 1 | 1 - 'ADVISED to ADVISED' | ADVISED | ADVISED || 0 | 0 - 'READY to READY' | READY | READY || 0 | 0 - 'LOCKED to LOCKED' | LOCKED | LOCKED || 0 | 0 - 'DELETED to ADVISED' | DELETED | ADVISED || 0 | 1 + stateChange | fromCmHandleState | toCmHandleState + 'ADVISED to READY' | ADVISED | READY + 'READY to LOCKED' | READY | LOCKED + 'ADVISED to LOCKED' | ADVISED | LOCKED + 'ADVISED to DELETING' | ADVISED | DELETING } - def 'Update and Publish Events on State Change from NO_EXISTING state to ADVISED'() { + def 'Update and Publish Events on State Change from non-existing to ADVISED'() { given: 'Cm Handle represented as YangModelCmHandle' yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: []) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) - then: 'state is saved using inventory persistence' - 1 * mockInventoryPersistence.saveCmHandle(yangModelCmHandle) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, ADVISED)) + then: 'CM-handle is saved using inventory persistence' + 1 * mockInventoryPersistence.saveCmHandleBatch(List.of(yangModelCmHandle)) and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in ADVISED state" } def 'Update and Publish Events on State Change from LOCKED to ADVISED'() { @@ -83,69 +110,62 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { lockReason: CompositeState.LockReason.builder().lockReasonCategory(MODULE_SYNC_FAILED).details('some lock details').build()) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, ADVISED)) then: 'state is saved using inventory persistence and old lock reason details are retained' - 1 * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) >> { + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args -> { - assert (args[1] as CompositeState).lockReason.details == 'some lock details' + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).lockReason.details == 'some lock details' } } and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in ADVISED state" } - def 'Update and Publish Events on State Change from DELETING to ADVISED'() { - given: 'Cm Handle represented as YangModelCmHandle in DELETING state' - yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) - when: 'update state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, ADVISED) - then: 'the cm handle is saved using inventory persistence' - 1 * mockInventoryPersistence.saveCmHandle(yangModelCmHandle) - and: 'event service is called to publish event' - 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) - } - - def 'Update and Publish Events on State Change to READY'() { + def 'Update and Publish Events on State Change to from ADVISED to READY'() { given: 'Cm Handle represented as YangModelCmHandle' compositeState = new CompositeState(cmHandleState: ADVISED) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) and: 'global sync flag is set' compositeState.setDataSyncEnabled(false) when: 'update cmhandle state is invoked' - objectUnderTest.updateCmHandleState(yangModelCmHandle, READY) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, READY)) then: 'state is saved using inventory persistence with expected dataSyncState' - 1 * mockInventoryPersistence.saveCmHandleState(cmHandleId, _) >> { + 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args-> { - def result = (args[1] as CompositeState) - assert result.dataSyncEnabled == false - assert result.dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.NONE_REQUESTED - + def cmHandleStatePerCmHandleId = args[0] as Map<String, CompositeState> + assert cmHandleStatePerCmHandleId.get(cmHandleId).dataSyncEnabled == false + assert cmHandleStatePerCmHandleId.get(cmHandleId).dataStores.operationalDataStore.dataStoreSyncState == DataStoreSyncState.NONE_REQUESTED } } and: 'event service is called to publish event' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) + and: 'a log entry is written' + assert getLogMessage(0) == "${cmHandleId} is now in READY state" } - def 'Update cmHandle state to "DELETING"' (){ + def 'Update cmHandle state from READY to DELETING' (){ given: 'cm Handle as Yang model' compositeState = new CompositeState(cmHandleState: READY) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'updating cm handle state to "DELETING"' - objectUnderTest.updateCmHandleState(yangModelCmHandle, DELETING) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, DELETING)) then: 'the cm handle state is as expected' yangModelCmHandle.getCompositeState().getCmHandleState() == DELETING and: 'method to persist cm handle state is called once' - 1 * mockInventoryPersistence.saveCmHandleState(yangModelCmHandle.getId(), yangModelCmHandle.getCompositeState()) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(Map.of(yangModelCmHandle.getId(), yangModelCmHandle.getCompositeState())) and: 'the method to publish Lcm event is called once' 1 * mockLcmEventsService.publishLcmEvent(cmHandleId, _, _) } - def 'Update cmHandle state to "DELETED"' (){ + def 'Update cmHandle state to DELETING to DELETED' (){ given: 'cm Handle with state "DELETING" as Yang model ' compositeState = new CompositeState(cmHandleState: DELETING) yangModelCmHandle = new YangModelCmHandle(id: cmHandleId, dmiProperties: [], publicProperties: [], compositeState: compositeState) when: 'updating cm handle state to "DELETED"' - objectUnderTest.updateCmHandleState(yangModelCmHandle, DELETED) + objectUnderTest.updateCmHandleStateBatch(Map.of(yangModelCmHandle, DELETED)) then: 'the cm handle state is as expected' yangModelCmHandle.getCompositeState().getCmHandleState() == DELETED and: 'the method to publish Lcm event is called once' @@ -157,14 +177,13 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('NO_CHANGE') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then: 'batch is empty and nothing to update' - 1 * mockInventoryPersistence.saveCmHandleBatch(_) >> { - args -> { - assert (args[0] as Collection<YangModelCmHandle>).size() == 0 - } - } + then: 'no changes are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) and: 'no event will be published' 0 * mockLcmEventsService.publishLcmEvent(*_) + and: 'no log entries are written' + assert logger.list.empty } def 'Batch of new cm handles provided'() { @@ -178,8 +197,13 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { assert (args[0] as Collection<YangModelCmHandle>).id.containsAll('cmhandle1', 'cmhandle2') } } + and: 'no state updates are persisted' + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in ADVISED state' + assert getLogMessage(1) == 'cmhandle2 is now in ADVISED state' } def 'Batch of existing cm handles is updated'() { @@ -187,14 +211,19 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('UPDATE') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then : 'existing cm handles composite state is persisted' + then: 'existing cm handles composite states are persisted' 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { args -> { - assert (args[0] as Map<String, CompositeState>).keySet().containsAll(['cmhandle1','cmhandle2']) + assert (args[0] as Map<String, CompositeState>).keySet().containsAll(['cmhandle1', 'cmhandle2']) } } + and: 'no new handles are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in READY state' + assert getLogMessage(1) == 'cmhandle2 is now in DELETING state' } def 'Batch of existing cm handles is deleted'() { @@ -202,14 +231,30 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def cmHandleStateMap = setupBatch('DELETED') when: 'updating a batch of changes' objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) - then : 'existing cm handles composite state is persisted' - 1 * mockInventoryPersistence.saveCmHandleStateBatch(_) >> { - args -> { - assert (args[0] as Map<String, CompositeState>).isEmpty() - } - } + then: 'state of deleted handles is not persisted' + 1 * mockInventoryPersistence.saveCmHandleStateBatch(EMPTY_MAP) + and: 'no new handles are persisted' + 1 * mockInventoryPersistence.saveCmHandleBatch(EMPTY_LIST) and: 'event service is called to publish events' 2 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'two log entries are written' + assert getLogMessage(0) == 'cmhandle1 is now in DELETED state' + assert getLogMessage(1) == 'cmhandle2 is now in DELETED state' + } + + def 'Log entries and events are not sent when an error occurs during persistence'() { + given: 'A batch of updated cm handles' + def cmHandleStateMap = setupBatch('UPDATE') + and: 'an error will be thrown when trying to persist' + mockInventoryPersistence.saveCmHandleStateBatch(_) >> { throw new RuntimeException() } + when: 'updating a batch of changes' + objectUnderTest.updateCmHandleStateBatch(cmHandleStateMap) + then: 'the exception is not handled' + thrown(RuntimeException) + and: 'no events are published' + 0 * mockLcmEventsService.publishLcmEvent(_, _, _) + and: 'no log entries are written' + assert logger.list.empty } def setupBatch(type) { @@ -217,26 +262,31 @@ class LcmEventsCmHandleStateHandlerImplSpec extends Specification { def yangModelCmHandle1 = new YangModelCmHandle(id: 'cmhandle1', dmiProperties: [], publicProperties: []) def yangModelCmHandle2 = new YangModelCmHandle(id: 'cmhandle2', dmiProperties: [], publicProperties: []) - if ('NEW' == type) { - return [yangModelCmHandle1, yangModelCmHandle2] - } + switch (type) { + case 'NEW': + return [yangModelCmHandle1, yangModelCmHandle2] - if ('DELETED' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: READY) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): DELETED, (yangModelCmHandle2): DELETED] - } + case 'DELETED': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: READY) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): DELETED, (yangModelCmHandle2): DELETED] - if ('UPDATE' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): READY, (yangModelCmHandle2): DELETING] - } + case 'UPDATE': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): READY, (yangModelCmHandle2): DELETING] + + case 'NO_CHANGE': + yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) + yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) + return [(yangModelCmHandle1): ADVISED, (yangModelCmHandle2): READY] - if ('NO_CHANGE' == type) { - yangModelCmHandle1.compositeState = new CompositeState(cmHandleState: ADVISED) - yangModelCmHandle2.compositeState = new CompositeState(cmHandleState: READY) - return [(yangModelCmHandle1): ADVISED, (yangModelCmHandle2): READY] + default: + throw new IllegalArgumentException("batch type '${type}' not recognized") } } + + def getLogMessage(index) { + return logger.list[index].formattedMessage + } } diff --git a/cps-parent/pom.xml b/cps-parent/pom.xml index f97a40b223..a83278d6cd 100644 --- a/cps-parent/pom.xml +++ b/cps-parent/pom.xml @@ -122,13 +122,26 @@ <executions> <execution> <goals> - <goal>build-info</goal> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> + <groupId>io.github.git-commit-id</groupId> + <artifactId>git-commit-id-maven-plugin</artifactId> + <version>9.0.0</version> + <executions> + <execution> + <id>get-git-info</id> + <goals> + <goal>revision</goal> + </goals> + <phase>package</phase> + </execution> + </executions> + </plugin> + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> 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/csit/tests/cps-model-sync/cps-model-sync.robot b/csit/tests/cps-model-sync/cps-model-sync.robot index 514076f085..b4e61b30d8 100644 --- a/csit/tests/cps-model-sync/cps-model-sync.robot +++ b/csit/tests/cps-model-sync/cps-model-sync.robot @@ -78,17 +78,6 @@ Get CM Handle details and confirm it has been updated. END END -Delete cm handle - ${uri}= Set Variable ${ncmpInventoryBasePath}/v1/ch - ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} - ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${deletePayload} - Should Be Equal As Strings ${response.status_code} 200 - -Get cm handle details and confirm it has been deleted - ${uri}= Set Variable ${ncmpBasePath}/v1/ch/CmHandleForDelete - ${headers}= Create Dictionary Authorization=${auth} - ${response}= GET On Session CPS_URL ${uri} headers=${headers} expected_status=404 - Check if ietfYang-PNFDemo is READY ${uri}= Set Variable ${ncmpBasePath}/v1/ch/ietfYang-PNFDemo ${headers}= Create Dictionary Authorization=${auth} @@ -107,6 +96,17 @@ Get modules for registered data node END END +Delete cm handle + ${uri}= Set Variable ${ncmpInventoryBasePath}/v1/ch + ${headers}= Create Dictionary Content-Type=application/json Authorization=${auth} + ${response}= POST On Session CPS_URL ${uri} headers=${headers} data=${deletePayload} + Should Be Equal As Strings ${response.status_code} 200 + +Get cm handle details and confirm it has been deleted + ${uri}= Set Variable ${ncmpBasePath}/v1/ch/CmHandleForDelete + ${headers}= Create Dictionary Authorization=${auth} + ${response}= GET On Session CPS_URL ${uri} headers=${headers} expected_status=404 + *** Keywords *** Is CM Handle READY @@ -125,4 +125,4 @@ Count Items In JSON Response [Arguments] ${response} ${json_data}= Evaluate json.loads('${response.content.decode("utf-8")}') json ${number_of_items}= Get Length ${json_data} - RETURN ${number_of_items}
\ No newline at end of file + RETURN ${number_of_items} diff --git a/docker-compose/config/endurance.env b/docker-compose/config/endurance.env new file mode 100644 index 0000000000..0ca1a1149a --- /dev/null +++ b/docker-compose/config/endurance.env @@ -0,0 +1,35 @@ +DB_CONTAINER_NAME=endurance-dbpostgresql +DB_PORT=5433 + +NGINX_CONTAINER_NAME=endurance-nginx-loadbalancer +CPS_CORE_PORT=8884 + +ZOOKEEPER_CONTAINER_NAME=endurance-zookeeper +ZOOKEEPER_PORT=2182 + +KAFKA_CONTAINER_NAME=endurance-kafka +KAFKA_PORT=9093 + +NCMP_DMI_PLUGIN_CONTAINER_NAME=endurance-ncmp-dmi-plugin +DMI_PORT=8786 + +NCMP_DMI_PLUGIN_DEMO_AND_CSIT_STUB_CONTAINER_NAME=endurance-ncmp-dmi-plugin-demo-and-csit-stub +DMI_DEMO_STUB_PORT=8787 + +POLICY_EXECUTOR_STUB_CONTAINER_NAME=endurance-policy-executor-stub +POLICY_EXECUTOR_STUB_PORT=8788 + +PROMETHEUS_CONTAINER_NAME=endurance-prometheus +PROMETHEUS_PORT=9091 + +GRAFANA_CONTAINER_NAME=endurance-grafana +GRAFANA_PORT=3001 + +KAFKA_UI_CONTAINER_NAME=endurance-kafka-ui +KAFKA_UI_PORT=8090 + +JAEGER_SERVICE_CONTAINER_NAME=endurance-jaeger-service +JAEGER_SERVICE_PORT=16687 + +CPS_NCMP_CACHES_CLUSTER_NAME=endurance-cps-and-ncmp-common-cache-cluster +CPS_NCMP_INSTANCE_CONFIG_NAME=endurance-cps-and-ncmp-hazelcast-instance-config
\ No newline at end of file diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index e7703d8d68..feb58d849d 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -24,9 +24,11 @@ services: ### docker-compose --profile dmi-stub --profile policy-executor-stub up -d -> run CPS with stubbed dmi-plugin and policy executor stub (for policy executor service testing make POLICY_SERVICE_ENABLED "true") ### to disable notifications make notification.enabled to false & comment out kafka/zookeeper services ### ### DEBUG: Look for '### DEBUG' comments to enable CPS-NCMP debugging + ### docker-compose --profile dmi-stub --project-name endurance --env-file config/endurance.env up -d -> run CPS with stubbed dmi-plugin for endurance testing + ### docker-compose --profile dmi-stub --project-name endurance down --volumes dbpostgresql: - container_name: dbpostgresql + container_name: ${DB_CONTAINER_NAME:-dbpostgresql} image: postgres:14.1-alpine ports: - ${DB_PORT:-5432}:5432 @@ -80,7 +82,7 @@ services: ### DEBUG - ${CPS_CORE_DEBUG_PORT:-5005}:5005 nginx: - container_name: nginx-loadbalancer + container_name: ${NGINX_CONTAINER_NAME:-nginx-loadbalancer} image: nginx:latest ports: - ${CPS_CORE_PORT:-8883}:80 @@ -93,17 +95,17 @@ services: ### if kafka is not required comment out zookeeper and kafka ### zookeeper: image: confluentinc/cp-zookeeper:6.2.1 - container_name: zookeeper + container_name: ${ZOOKEEPER_CONTAINER_NAME:-zookeeper} ports: - - '2181:2181' + - ${ZOOKEEPER_PORT:-2181}:2181 environment: ZOOKEEPER_CLIENT_PORT: 2181 kafka: image: confluentinc/cp-kafka:6.2.1 - container_name: kafka + container_name: ${KAFKA_CONTAINER_NAME:-kafka} ports: - - '9092:9092' + - ${KAFKA_PORT:-9092}:9092 depends_on: - zookeeper environment: @@ -114,7 +116,7 @@ services: KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 ncmp-dmi-plugin: - container_name: ncmp-dmi-plugin + container_name: ${NCMP_DMI_PLUGIN_CONTAINER_NAME:-ncmp-dmi-plugin} image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/ncmp-dmi-plugin:${DMI_VERSION:-1.6.0-SNAPSHOT-latest} ports: - ${DMI_PORT:-8783}:8080 @@ -139,7 +141,7 @@ services: - dmi-service ncmp-dmi-plugin-demo-and-csit-stub: - container_name: ncmp-dmi-plugin-demo-and-csit-stub + container_name: ${NCMP_DMI_PLUGIN_DEMO_AND_CSIT_STUB_CONTAINER_NAME:-ncmp-dmi-plugin-demo-and-csit-stub} image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/dmi-plugin-demo-and-csit-stub:${DMI_DEMO_STUB_VERSION:-latest} ports: - ${DMI_DEMO_STUB_PORT:-8784}:8092 @@ -147,7 +149,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 @@ -158,19 +160,19 @@ services: - dmi-service policy-executor-stub: - container_name: policy-executor-stub + container_name: ${POLICY_EXECUTOR_STUB_CONTAINER_NAME:-policy-executor-stub} image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/policy-executor-stub:latest ports: - - 8785:8093 + - ${POLICY_EXECUTOR_STUB_PORT:-8785}:8093 restart: unless-stopped profiles: - policy-executor-stub prometheus: - container_name: prometheus + container_name: ${PROMETHEUS_CONTAINER_NAME:-prometheus} image: prom/prometheus:latest ports: - - 9090:9090 + - ${PROMETHEUS_PORT:-9090}:9090 restart: always volumes: - ./config/prometheus.yml:/etc/prometheus/prometheus.yml @@ -180,12 +182,12 @@ services: grafana: image: grafana/grafana-oss:latest user: "" - container_name: grafana + container_name: ${GRAFANA_CONTAINER_NAME:-grafana} depends_on: prometheus: condition: service_started ports: - - 3000:3000 + - ${GRAFANA_PORT:-3000}:3000 volumes: - ./config/grafana/provisioning/:/etc/grafana/provisioning/ - ./config/grafana/jvm-micrometer-dashboard.json:/var/lib/grafana/dashboards/jvm-micrometer-dashboard.json @@ -197,10 +199,10 @@ services: - monitoring kafka-ui: - container_name: kafka-ui + container_name: ${KAFKA_UI_CONTAINER_NAME:-kafka-ui} image: provectuslabs/kafka-ui:latest ports: - - 8089:8080 + - ${KAFKA_UI_PORT:-8089}:8080 environment: DYNAMIC_CONFIG_ENABLED: 'true' KAFKA_CLUSTERS_0_NAME: 'cps-kafka-local' @@ -209,10 +211,10 @@ services: - monitoring jaeger-service: - container_name: jaeger-service + container_name: ${JAEGER_SERVICE_CONTAINER_NAME:-jaeger-service} image: jaegertracing/all-in-one:latest ports: - - 16686:16686 + - ${JAEGER_SERVICE_PORT:-16686}:16686 restart: unless-stopped profiles: - tracing diff --git a/docs/api/swagger/ncmp/openapi-inventory.yaml b/docs/api/swagger/ncmp/openapi-inventory.yaml index a2c7af6fda..8552ad53e3 100644 --- a/docs/api/swagger/ncmp/openapi-inventory.yaml +++ b/docs/api/swagger/ncmp/openapi-inventory.yaml @@ -131,9 +131,19 @@ paths: - network-cm-proxy-inventory /v1/ch/searches: post: - description: "Query and get CMHandleIds for additional properties, public properties\ - \ and registered DMI plugin (DMI plugin, DMI data plugin, DMI model plugin)." + description: "Query and get CMHandle references for additional properties, public\ + \ properties and registered DMI plugin (DMI plugin, DMI data plugin, DMI model\ + \ plugin)." operationId: searchCmHandleIds + parameters: + - description: Boolean parameter to determine if returned value(s) will be cmHandle + Ids or Alternate Ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean requestBody: content: application/json: @@ -182,6 +192,15 @@ components: schema: example: my-dmi-plugin type: string + outputAlternateIdOptionInQuery: + description: Boolean parameter to determine if returned value(s) will be cmHandle + Ids or Alternate Ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean responses: NoContent: content: {} diff --git a/docs/api/swagger/ncmp/openapi.yaml b/docs/api/swagger/ncmp/openapi.yaml index f93395a6db..aa732c8566 100644 --- a/docs/api/swagger/ncmp/openapi.yaml +++ b/docs/api/swagger/ncmp/openapi.yaml @@ -1129,7 +1129,7 @@ paths: - network-cm-proxy /v1/ch/id-searches: post: - description: Execute cm handle query search and return a list of cm handle ids. + description: Execute cm handle query search and return a list of cm handle references. Any number of conditions can be applied. To be included in the result a cm-handle must fulfill ALL the conditions. An empty collection will be returned in the case that the cm handle does not match a condition. For more on cm handle @@ -1140,6 +1140,15 @@ paths: Path Read the Docs</a>. The cm handle ancestor is automatically returned for this query. operationId: searchCmHandleIds + parameters: + - description: Boolean parameter to determine if returned value(s) will be cmHandle + Ids or Alternate Ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean requestBody: content: application/json: @@ -1608,6 +1617,15 @@ components: schema: example: 2024-01-22 type: string + outputAlternateIdOptionInQuery: + description: Boolean parameter to determine if returned value(s) will be cmHandle + Ids or Alternate Ids for a given query + in: query + name: outputAlternateId + required: false + schema: + default: false + type: boolean dataSyncEnabled: description: Is used to enable or disable the data synchronization flag in: query diff --git a/docs/deployment.rst b/docs/deployment.rst index b49bc71985..3f0c1eddc5 100644 --- a/docs/deployment.rst +++ b/docs/deployment.rst @@ -31,6 +31,12 @@ This helps the JVM make the best use of the allocated resources while leaving en 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>`_. diff --git a/docs/release-notes.rst b/docs/release-notes.rst index 24ba54fe43..c5b00d6888 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -1618,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 @@ -1731,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/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/ncmp/CmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy index 10a9f15e21..00ce38fa2d 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 @@ -20,7 +20,7 @@ package org.onap.cps.integration.functional.ncmp -import org.apache.kafka.common.TopicPartition +import org.apache.kafka.clients.consumer.KafkaConsumer import org.apache.kafka.common.serialization.StringDeserializer import org.onap.cps.integration.KafkaTestContainer import org.onap.cps.integration.base.CpsIntegrationSpecBase @@ -35,54 +35,69 @@ import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory 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) + static KafkaConsumer kafkaConsumer def setup() { objectUnderTest = networkCmProxyInventoryFacade + subscribeAndClearPreviousMessages() } - def 'CM Handle registration is successful.'() { + def cleanupSpec() { + kafkaConsumer.unsubscribe() + kafkaConsumer.close() + } + + def 'CM Handle registration.'() { given: 'DMI will return modules when requested' dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] - - and: 'consumer subscribed to topic' - kafkaConsumer.subscribe(['ncmp-events']) + dmiDispatcher1.moduleNamesPerCmHandleId[uniqueId] = ['M1', 'M2'] 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 + + then: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - and: 'CM-handle goes to READY state after module sync' + then: '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' - def message = kafkaConsumer.poll(Duration.ofMillis(10000)) - def records = message.records(new TopicPartition('ncmp-events', 0)) + and: 'the CM-handle has expected modules' + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(uniqueId).moduleName.sort() - and: 'the newest lcm event notification is received with READY state' - def notificationMessage = jsonObjectMapper.convertJsonString(records.last().value().toString(), LcmEvent) - assert notificationMessage.event.newValues.cmHandleState.value() == 'READY' + then: 'get the latest messages' + def consumerRecords = getLatestConsumerRecords() - and: 'the CM-handle has expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() + and: 'both converted messages are for the correct cm handle' + def notificationMessages = [] + for (def consumerRecord : consumerRecords) { + notificationMessages.add(jsonObjectMapper.convertJsonString(consumerRecord.value().toString(), LcmEvent)) + } + assert notificationMessages.event.cmHandleId == [ uniqueId, uniqueId ] + + and: 'the oldest event is about the update to ADVISED state' + notificationMessages[0].event.newValues.cmHandleState.value() == 'ADVISED' + + and: 'the next event is about update to READY state' + notificationMessages[1].event.newValues.cmHandleState.value() == 'READY' 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 +107,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 +135,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 +172,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 +194,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 +208,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 -> @@ -198,4 +226,23 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { cleanup: 'deregister CM handles' deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2']) } + + def subscribeAndClearPreviousMessages() { + kafkaConsumer = KafkaTestContainer.getConsumer('test-group', StringDeserializer.class) + kafkaConsumer.subscribe(['ncmp-events']) + kafkaConsumer.poll(Duration.ofMillis(500)) + } + + def getLatestConsumerRecords() { + def consumerRecords = [] + def retryAttempts = 10 + while (consumerRecords.size() < 2) { + retryAttempts-- + consumerRecords.addAll(kafkaConsumer.poll(Duration.ofMillis(100))) + if (retryAttempts == 0) + break + } + consumerRecords + } + } 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 1e1af556f1..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 { @@ -65,15 +67,20 @@ class RestApiSpec extends CpsIntegrationSpecBase { ] }""".formatted(moduleName) expect: "a search for module ${moduleName} returns expected CM handles" - mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) + mvc.perform(post('/ncmp/v1/ch/id-searches'+outputAlternateId).contentType(MediaType.APPLICATION_JSON).content(requestBodyWithModuleCondition)) .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) - .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); + .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandleReferences.toArray()))) + .andExpect(jsonPath('$', hasSize(expectedCmHandleReferences.size()))); where: - moduleName || expectedCmHandles - 'M1' || ['ch-1', 'ch-2', 'ch-3'] - 'M2' || ['ch-1', 'ch-2'] - 'M3' || ['ch-3'] + moduleName | outputAlternateId || expectedCmHandleReferences + 'M1' | '?outputAlternateId=false' || ['ch-1', 'ch-2', 'ch-3'] + 'M2' | '?outputAlternateId=false' || ['ch-1', 'ch-2'] + 'M3' | '?outputAlternateId=false' || ['ch-3'] + 'M1' | '?outputAlternateId=true' || ['alt-1', 'alt-2', 'alt-3'] + 'M2' | '?outputAlternateId=true' || ['alt-1', 'alt-2'] + 'M3' | '?outputAlternateId=true' || ['alt-3'] + 'M1' | '' || ['ch-1', 'ch-2', 'ch-3'] + } def 'Search for CM Handles using Cps Path Query.'() { 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/java/org/onap/cps/integration/KafkaTestContainer.java b/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java index d41f752912..ff4aec4175 100644 --- a/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java +++ b/integration-test/src/test/java/org/onap/cps/integration/KafkaTestContainer.java @@ -21,6 +21,7 @@ package org.onap.cps.integration; import java.util.HashMap; import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.common.serialization.StringDeserializer; @@ -33,11 +34,12 @@ import org.testcontainers.utility.DockerImageName; * This ensures only one instance of Kafka container across the integration tests. * Avoid unnecessary resource and time consumption. */ +@Slf4j public class KafkaTestContainer extends KafkaContainer { private static final String IMAGE_NAME_AND_VERSION = "registry.nordix.org/onaptest/confluentinc/cp-kafka:6.2.1"; - private static KafkaTestContainer kafkaTestContainer; + private static volatile KafkaTestContainer kafkaTestContainer; private KafkaTestContainer() { super(DockerImageName.parse(IMAGE_NAME_AND_VERSION).asCompatibleSubstituteFor("confluentinc/cp-kafka")); @@ -51,8 +53,15 @@ public class KafkaTestContainer extends KafkaContainer { */ public static KafkaTestContainer getInstance() { if (kafkaTestContainer == null) { - kafkaTestContainer = new KafkaTestContainer(); - Runtime.getRuntime().addShutdownHook(new Thread(kafkaTestContainer::close)); + synchronized (KafkaTestContainer.class) { + if (kafkaTestContainer == null) { + kafkaTestContainer = new KafkaTestContainer(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + log.info("Shutting down KafkaTestContainer..."); + kafkaTestContainer.stop(); + })); + } + } } return kafkaTestContainer; } @@ -63,8 +72,11 @@ public class KafkaTestContainer extends KafkaContainer { @Override public void start() { - super.start(); - System.setProperty("spring.kafka.properties.bootstrap.servers", kafkaTestContainer.getBootstrapServers()); + if (!isRunning()) { + super.start(); + System.setProperty("spring.kafka.properties.bootstrap.servers", getBootstrapServers()); + log.info("KafkaTestContainer started at {}", getBootstrapServers()); + } } @Override @@ -78,8 +90,9 @@ public class KafkaTestContainer extends KafkaContainer { configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaTestContainer.getBootstrapServers()); configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, valueDeserializer); - configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + configProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest"); configProps.put(ConsumerConfig.GROUP_ID_CONFIG, consumerGroupId); + configProps.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, Integer.MAX_VALUE); return configProps; } 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 853797aac5..b786a3d4c5 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -179,7 +179,7 @@ ncmp: timers: advised-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/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..53f7629cac 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: '20m', + teardownTimeout: '20m', scenarios: { passthrough_read_scenario: { executor: 'constant-vus', 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 { |