diff options
Diffstat (limited to 'integration-test')
36 files changed, 1440 insertions, 471 deletions
diff --git a/integration-test/pom.xml b/integration-test/pom.xml index a3112661e9..7ac9460f5e 100644 --- a/integration-test/pom.xml +++ b/integration-test/pom.xml @@ -23,7 +23,7 @@ <parent> <groupId>org.onap.cps</groupId> <artifactId>cps-parent</artifactId> - <version>3.5.1-SNAPSHOT</version> + <version>3.5.5-SNAPSHOT</version> <relativePath>../cps-parent/pom.xml</relativePath> </parent> <modelVersion>4.0.0</modelVersion> @@ -107,30 +107,10 @@ <artifactId>spock</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-json</artifactId> + <scope>test</scope> + </dependency> </dependencies> - - <profiles> - <!-- Performance tests are run with maven-failsafe-plugin using a separate profile, so they will - not affect Jacoco coverage. Heap size is set here to ensure consistent test environment. --> - <profile> - <id>include-performance</id> - <properties> - <failsafeArgLine>-Xms512m -Xmx512m</failsafeArgLine> - </properties> - <build> - <plugins> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-failsafe-plugin</artifactId> - <configuration> - <includes> - <include>**/*PerfTest.java</include> - </includes> - </configuration> - </plugin> - </plugins> - </build> - </profile> - </profiles> - </project> 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 0afdfa1066..02a10cfa6b 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 @@ -21,6 +21,7 @@ package org.onap.cps.integration.base +import com.hazelcast.collection.ISet import okhttp3.mockwebserver.MockWebServer import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService @@ -37,21 +38,23 @@ import org.onap.cps.ncmp.impl.data.NetworkCmProxyQueryService import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.ParameterizedCmHandleQueryService import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncService import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher +import org.onap.cps.ri.repository.DataspaceRepository +import org.onap.cps.ri.utils.SessionManager import org.onap.cps.spi.exceptions.DataspaceNotFoundException import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.repository.DataspaceRepository -import org.onap.cps.spi.utils.SessionManager -import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.EnableAutoConfiguration import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc 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 @@ -59,11 +62,7 @@ import spock.lang.Specification import spock.util.concurrent.PollingConditions import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter - -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 +import java.util.concurrent.BlockingQueue @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, classes = [CpsDataspaceService]) @Testcontainers @@ -71,7 +70,8 @@ import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY @AutoConfigureMockMvc @EnableJpaRepositories(basePackageClasses = [DataspaceRepository]) @ComponentScan(basePackages = ['org.onap.cps']) -@EntityScan('org.onap.cps.spi.entities') +@EntityScan('org.onap.cps.ri.models') +@ActiveProfiles('module-sync-delayed') abstract class CpsIntegrationSpecBase extends Specification { @Shared @@ -117,6 +117,12 @@ abstract class CpsIntegrationSpecBase extends Specification { ModuleSyncWatchdog moduleSyncWatchdog @Autowired + ModuleSyncService moduleSyncService + + @Autowired + BlockingQueue<DataNode> moduleSyncWorkQueue + + @Autowired JsonObjectMapper jsonObjectMapper @Autowired @@ -125,16 +131,31 @@ abstract class CpsIntegrationSpecBase extends Specification { @Autowired AlternateIdMatcher alternateIdMatcher - MockWebServer mockDmiServer = null - DmiDispatcher dmiDispatcher = new DmiDispatcher() + @Autowired + ISet<String> moduleSetTagsBeingProcessed - def DMI_URL = null + @Value('${ncmp.policy-executor.server.port:8080}') + private String policyServerPort; + + MockWebServer mockDmiServer1 = new MockWebServer() + MockWebServer mockDmiServer2 = new MockWebServer() + MockWebServer mockPolicyServer = new MockWebServer() + + DmiDispatcher dmiDispatcher1 = new DmiDispatcher() + DmiDispatcher dmiDispatcher2 = new DmiDispatcher() + + PolicyDispatcher policyDispatcher = new PolicyDispatcher(); + + def DMI1_URL = null + def DMI2_URL = null static NO_MODULE_SET_TAG = '' + static NO_ALTERNATE_ID = '' static GENERAL_TEST_DATASPACE = 'generalTestDataspace' static BOOKSTORE_SCHEMA_SET = 'bookstoreSchemaSet' + static MODULE_SYNC_WAIT_TIME_IN_SECONDS = 10 - def static initialized = false + static initialized = false def now = OffsetDateTime.now() def setup() { @@ -143,14 +164,24 @@ abstract class CpsIntegrationSpecBase extends Specification { createStandardBookStoreSchemaSet(GENERAL_TEST_DATASPACE) initialized = true } - mockDmiServer = new MockWebServer() - mockDmiServer.setDispatcher(dmiDispatcher) - mockDmiServer.start() - DMI_URL = String.format("http://%s:%s", mockDmiServer.getHostName(), mockDmiServer.getPort()) + mockDmiServer1.setDispatcher(dmiDispatcher1) + mockDmiServer1.start() + + mockDmiServer2.setDispatcher(dmiDispatcher2) + mockDmiServer2.start() + + mockPolicyServer.setDispatcher(policyDispatcher) + mockPolicyServer.start(Integer.valueOf(policyServerPort)) + + DMI1_URL = String.format("http://%s:%s", mockDmiServer1.getHostName(), mockDmiServer1.getPort()) + DMI2_URL = String.format("http://%s:%s", mockDmiServer2.getHostName(), mockDmiServer2.getPort()) } def cleanup() { - mockDmiServer.shutdown() + mockDmiServer1.shutdown() + mockDmiServer2.shutdown() + mockPolicyServer.shutdown() + moduleSetTagsBeingProcessed.clear() } def static readResourceDataFile(filename) { @@ -216,27 +247,49 @@ abstract class CpsIntegrationSpecBase extends Specification { // *** NCMP Integration Test Utilities *** def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag) { - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag) - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate])) + registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, NO_ALTERNATE_ID) + } + + def registerCmHandle(dmiPlugin, cmHandleId, moduleSetTag, alternateId) { + registerCmHandleWithoutWaitForReady(dmiPlugin, cmHandleId, moduleSetTag, alternateId) moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - new PollingConditions().within(3, () -> { + 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 registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles, offset) { + def cmHandles = [] + def id = offset + def moduleReferences = (1..200).collect { moduleSetTag + '_Module_' + it.toString() } + (1..numberOfCmHandles).each { + def ncmpServiceCmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch-'+id, moduleSetTag: moduleSetTag, alternateId: NO_ALTERNATE_ID) + cmHandles.add(ncmpServiceCmHandle) + dmiDispatcher1.moduleNamesPerCmHandleId[ncmpServiceCmHandle.cmHandleId] = moduleReferences + dmiDispatcher2.moduleNamesPerCmHandleId[ncmpServiceCmHandle.cmHandleId] = moduleReferences + id++ + } + 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, offset) { + def cmHandleIds = [] + def id = offset + (1..numberOfCmHandles).each { cmHandleIds.add('ch-' + id++) } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } + } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy index 6676cb74c2..35a7b6a7c2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy @@ -20,16 +20,18 @@ package org.onap.cps.integration.base -import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile - -import org.springframework.http.HttpHeaders -import java.util.regex.Matcher +import groovy.json.JsonSlurper import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest +import org.springframework.http.HttpHeaders import org.springframework.http.HttpStatus import org.springframework.http.MediaType +import java.util.regex.Matcher + +import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile + /** * This class simulates responses from the DMI server in NCMP integration tests. * @@ -54,51 +56,95 @@ class DmiDispatcher extends Dispatcher { def isAvailable = true - Map<String, List<String>> moduleNamesPerCmHandleId = [:] + def jsonSlurper = new JsonSlurper() + def moduleNamesPerCmHandleId = [:] + def receivedSubJobs = [:] + def lastAuthHeaderReceived + def dmiResourceDataUrl @Override MockResponse dispatch(RecordedRequest request) { if (!isAvailable) { - return new MockResponse().setResponseCode(HttpStatus.SERVICE_UNAVAILABLE.value()) + return mockResponse(HttpStatus.SERVICE_UNAVAILABLE) + } + if (request.path == '/actuator/health') { + return mockResponseWithBody(HttpStatus.OK, '{"status":"UP"}') } + + lastAuthHeaderReceived = request.getHeader('Authorization') switch (request.path) { - case ~/^\/dmi\/v1\/ch\/(.*)\/modules$/: + // get module references for a CM-handle + case ~'^/dmi/v1/ch/(.*)/modules$': def cmHandleId = Matcher.lastMatcher[0][1] return getModuleReferencesResponse(cmHandleId) - case ~/^\/dmi\/v1\/ch\/(.*)\/moduleResources$/: + // get module resources for a CM-handle + case ~'^/dmi/v1/ch/(.*)/moduleResources$': def cmHandleId = Matcher.lastMatcher[0][1] return getModuleResourcesResponse(cmHandleId) + // pass-through data operation for a CM-handle + case ~'^/dmi/v1/ch/(.*)/data/ds/(.*)$': + dmiResourceDataUrl = request.path + return mockResponseWithBody(HttpStatus.OK, '{}') + + // legacy pass-through batch data operation + case ~'^/dmi/v1/data$': + return mockResponseWithBody(HttpStatus.ACCEPTED, '{}') + + // get data job status + case ~'^/dmi/v1/cmwriteJob/dataProducer/(.*)/dataProducerJob/(.*)/status$': + return mockResponseWithBody(HttpStatus.OK, '{"status":"status details from mock service"}') + + // get data job result + case ~'^/dmi/v1/cmwriteJob/dataProducer/(.*)/dataProducerJob/(.*)/result(.*)$': + return mockResponseWithBody(HttpStatus.OK, '{ "result": "some result"}') + + // get write sub job response + case ~'^/dmi/v1/cmwriteJob(.*)$': + return mockWriteJobResponse(request) + default: - throw new IllegalArgumentException('Mock DMI does not handle path ' + request.path) + throw new IllegalArgumentException('Mock DMI does not implement endpoint ' + request.path) } } - private getModuleReferencesResponse(cmHandleId) { + def mockWriteJobResponse(request) { + def destination = Matcher.lastMatcher[0][1] + def subJobWriteRequest = jsonSlurper.parseText(request.getBody().readUtf8()) + this.receivedSubJobs.put(destination, subJobWriteRequest) + def response = '{"subJobId":"some sub job id", "dmiServiceName":"some dmi service name", "dataProducerId":"some data producer id"}' + return mockResponseWithBody(HttpStatus.OK, response) + } + + def getModuleReferencesResponse(cmHandleId) { def moduleReferences = '{"schemas":[' + getModuleNamesForCmHandle(cmHandleId).collect { MODULE_REFERENCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) }.join(',') + ']}' - return mockOkResponseWithBody(moduleReferences) + return mockResponseWithBody(HttpStatus.OK, moduleReferences) } - private getModuleResourcesResponse(cmHandleId) { + def getModuleResourcesResponse(cmHandleId) { def moduleResources = '[' + getModuleNamesForCmHandle(cmHandleId).collect { MODULE_RESOURCES_RESPONSE_TEMPLATE.replaceAll("<MODULE_NAME>", it) }.join(',') + ']' - return mockOkResponseWithBody(moduleResources) + return mockResponseWithBody(HttpStatus.OK, moduleResources) } - private getModuleNamesForCmHandle(cmHandleId) { + def getModuleNamesForCmHandle(cmHandleId) { if (!moduleNamesPerCmHandleId.containsKey(cmHandleId)) { throw new IllegalArgumentException('Mock DMI has no modules configured for ' + cmHandleId) } return moduleNamesPerCmHandleId.get(cmHandleId) } - private static mockOkResponseWithBody(responseBody) { + def static mockResponse(status) { + return new MockResponse().setResponseCode(status.value()) + } + + def static mockResponseWithBody(status, responseBody) { return new MockResponse() - .setResponseCode(HttpStatus.OK.value()) + .setResponseCode(status.value()) .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) .setBody(responseBody) } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy new file mode 100644 index 0000000000..b08d1c1548 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/PolicyDispatcher.groovy @@ -0,0 +1,78 @@ +/* + * ============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.base + +import okhttp3.mockwebserver.Dispatcher +import okhttp3.mockwebserver.MockResponse +import okhttp3.mockwebserver.RecordedRequest +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper + +import java.util.concurrent.TimeUnit + +/** + * This class simulates responses from the Policy Execution server in NCMP integration tests. + */ +class PolicyDispatcher extends Dispatcher { + + def objectMapper = new ObjectMapper() + def expectedAuthorizationToken = 'ABC' + def allowAll = true; // Prevents legacy test being affected + + @Override + MockResponse dispatch(RecordedRequest recordedRequest) { + + if (!allowAll && !recordedRequest.getHeader('Authorization').contains(expectedAuthorizationToken)) { + return new MockResponse().setResponseCode(401) + } + + if (recordedRequest.path != '/policy-executor/api/v1/execute') { + return new MockResponse().setResponseCode(400) + } + + def body = objectMapper.readValue(recordedRequest.getBody().readUtf8(), Map.class) + def targetIdentifier = body.get('requests').get(0).get('data').get('targetIdentifier') + def responseAsMap = [:] + responseAsMap.put('decisionId',1) + if (targetIdentifier == "mock slow response") { + TimeUnit.SECONDS.sleep(2) // One second more then configured readTimeoutInSeconds + } + if (allowAll || targetIdentifier == 'fdn1') { + responseAsMap.put('decision','allow') + responseAsMap.put('message','') + } else { + responseAsMap.put('decision','deny from mock server (dispatcher)') + responseAsMap.put('message','I only like fdn1') + } + def responseAsString = objectMapper.writeValueAsString(responseAsMap) + + return mockResponseWithBody(HttpStatus.OK, responseAsString) + } + + static mockResponseWithBody(status, responseBody) { + return new MockResponse() + .setResponseCode(status.value()) + .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .setBody(responseBody) + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy deleted file mode 100644 index 0f442a82f1..0000000000 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleCreateSpec.groovy +++ /dev/null @@ -1,189 +0,0 @@ -/* - * ============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 - -import org.apache.kafka.common.TopicPartition -import org.apache.kafka.common.serialization.StringDeserializer -import org.onap.cps.integration.KafkaTestContainer -import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade -import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse -import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration -import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle -import org.onap.cps.ncmp.events.lcm.v1.LcmEvent -import org.onap.cps.ncmp.impl.inventory.models.CmHandleState -import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory -import spock.util.concurrent.PollingConditions - -import java.time.Duration -import java.time.OffsetDateTime - -class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase { - - NetworkCmProxyInventoryFacade objectUnderTest - - def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class) - - def setup() { - objectUnderTest = networkCmProxyInventoryFacade - } - - def 'CM Handle registration is successful.'() { - given: 'DMI will return modules when requested' - dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] - - and: 'consumer subscribed to topic' - kafkaConsumer.subscribe(['ncmp-events']) - - when: 'a CM-handle is registered for creation' - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate]) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - - then: 'registration gives successful response' - assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] - - and: 'CM-handle is initially in ADVISED state' - assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState - - when: 'module sync runs' - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - - then: 'CM-handle goes to READY state' - new PollingConditions().within(3, () -> { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState - }) - - and: 'the messages is polled' - def message = kafkaConsumer.poll(Duration.ofMillis(10000)) - def records = message.records(new TopicPartition('ncmp-events', 0)) - - 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' - - and: 'the CM-handle has expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - - cleanup: 'deregister CM handle' - deregisterCmHandle(DMI_URL, 'ch-1') - } - - def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() { - given: 'DMI is not available to handle requests' - dmiDispatcher.isAvailable = false - - when: 'a CM-handle is registered for creation' - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate]) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - - and: 'module sync runs' - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - - then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED' - new PollingConditions().within(3, () -> { - def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState('ch-1') - assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED - assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_SYNC_FAILED - }) - - and: 'CM-handle has no modules' - assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty - - cleanup: 'deregister CM handle' - deregisterCmHandle(DMI_URL, 'ch-1') - } - - def 'Create a CM-handle with existing moduleSetTag.'() { - given: 'DMI will return modules when requested' - dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] - and: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"' - registerCmHandle(DMI_URL, 'ch-1', 'A') - registerCmHandle(DMI_URL, 'ch-2', 'B') - - 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: DMI_URL, createdCmHandles: [cmHandleToCreate])) - - then: 'the CM-handle goes to READY state after module sync' - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - new PollingConditions().within(3, () -> { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-3').cmHandleState - }) - - and: 'the CM-handle has expected moduleSetTag' - assert objectUnderTest.getNcmpServiceCmHandle('ch-3').moduleSetTag == 'B' - - and: 'the CM-handle has expected modules from module set "B": M1 and M3' - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort() - - cleanup: 'deregister CM handles' - deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2', 'ch-3']) - } - - def 'CM Handle retry after failed module sync.'() { - given: 'DMI is not initially available to handle requests' - dmiDispatcher.isAvailable = false - - when: 'CM-handles are registered for creation' - def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')] - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: cmHandlesToCreate) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) - and: 'module sync runs' - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - then: 'CM-handles go to LOCKED state' - new PollingConditions().within(3, () -> { - assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.LOCKED - assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.LOCKED - }) - - when: 'we wait for LOCKED CM handle retry time (actually just subtract 3 minutes from handles lastUpdateTime)' - overrideCmHandleLastUpdateTime('ch-1', OffsetDateTime.now().minusMinutes(3)) - overrideCmHandleLastUpdateTime('ch-2', OffsetDateTime.now().minusMinutes(3)) - and: 'failed CM handles are reset' - moduleSyncWatchdog.resetPreviouslyFailedCmHandles() - then: 'CM-handles are ADVISED state' - assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.ADVISED - assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.ADVISED - - when: 'DMI is available for retry' - dmiDispatcher.isAvailable = true - and: 'DMI will return expected modules' - dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] - and: 'module sync runs' - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() - then: 'CM-handles go to READY state' - new PollingConditions().within(3, () -> { - assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.READY - assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.READY - }) - and: 'CM-handles have expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort() - and: 'CM-handles have expected module set tags (blank)' - assert objectUnderTest.getNcmpServiceCmHandle('ch-1').moduleSetTag == '' - assert objectUnderTest.getNcmpServiceCmHandle('ch-2').moduleSetTag == '' - - cleanup: 'deregister CM handle' - deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2']) - } -} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAnchorServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/AnchorServiceIntegrationSpec.groovy index 26857799a1..240ff5114b 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsAnchorServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/AnchorServiceIntegrationSpec.groovy @@ -19,18 +19,18 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.cps import java.time.OffsetDateTime import org.onap.cps.api.CpsAnchorService -import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.integration.base.FunctionalSpecBase import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.AnchorNotFoundException import org.onap.cps.utils.ContentType -class CpsAnchorServiceIntegrationSpec extends CpsIntegrationSpecBase { +class AnchorServiceIntegrationSpec extends FunctionalSpecBase { CpsAnchorService objectUnderTest diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy index 779c0b84c4..d49931eb7e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy @@ -19,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.cps import org.onap.cps.api.CpsDataService import org.onap.cps.integration.base.FunctionalSpecBase @@ -39,7 +39,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.DIRECT_CHILDREN_ONLY import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS -class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { +class DataServiceIntegrationSpec extends FunctionalSpecBase { CpsDataService objectUnderTest def originalCountBookstoreChildNodes @@ -224,7 +224,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { given: 'a new (multiple-data-tree:invoice) datanodes' def json = '{"bookstore-address":[{"bookstore-name":"Easons","address":"Bangalore,India","postal-code":"560043"}]}' when: 'the new list elements are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/', json, now, ContentType.JSON) then: 'they can be retrieved by their xpaths' objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore-address[@bookstore-name="Easons"]', INCLUDE_ALL_DESCENDANTS) and: 'there is one extra datanode' @@ -239,7 +239,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { given: 'two new (categories) data nodes' def json = '{"categories": [ {"code":"new1"}, {"code":"new2" } ] }' when: 'the new list elements are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'they can be retrieved by their xpaths' objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', DIRECT_CHILDREN_ONLY).size() == 1 objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new2"]', DIRECT_CHILDREN_ONLY).size() == 1 @@ -256,7 +256,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { given: 'two (categories) data nodes, one new and one existing' def json = '{"categories": [ {"code":"1"}, {"code":"new1"} ] }' when: 'attempt to save the list element' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'an exception that (one cps paths is) already defined is thrown ' def exceptionThrown = thrown(AlreadyDefinedException) exceptionThrown.alreadyDefinedObjectNames == ['/bookstore/categories[@code=\'1\']' ] as Set @@ -270,7 +270,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { given: 'a new (categories) data nodes' def json = '{"categories": [ {"code":"new1"} ] }' and: 'the new list element is saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) when: 'the new element is deleted' objectUnderTest.deleteListOrListElement(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', now) then: 'the original number of data nodes is restored' @@ -281,7 +281,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { given: 'two new (categories) data nodes in a single batch' def json = '{"categories": [ {"code":"new1"}, {"code":"new2"} ] }' when: 'the batches of new list element(s) are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'they can be retrieved by their xpaths' assert objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new1"]', DIRECT_CHILDREN_ONLY).size() == 1 assert objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/categories[@code="new2"]', DIRECT_CHILDREN_ONLY).size() == 1 @@ -298,7 +298,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { given: 'one existing and one new (categories) data nodes in a single batch' def json = '{"categories": [ {"code":"new1"}, {"code":"1"} ] }' when: 'the batches of new list element(s) are saved' - objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) + objectUnderTest.saveListElements(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now, ContentType.JSON) then: 'an already defined (batch) exception is thrown for the existing path' def exceptionThrown = thrown(AlreadyDefinedException) assert exceptionThrown.alreadyDefinedObjectNames == ['/bookstore/categories[@code=\'1\']' ] as Set @@ -396,7 +396,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { objectUnderTest.saveData(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1 , '/bookstore', json, now) when: 'the webinfo (container) is updated' json = '{"webinfo": {"domain-name":"newdomain.com" ,"contact-email":"info@newdomain.com" }}' - objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', json, now) + objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', json, now, ContentType.JSON) then: 'webinfo has been updated with teh new details' def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore/webinfo', DIRECT_CHILDREN_ONLY) result.leaves.'domain-name'[0] == 'newdomain.com' @@ -408,7 +408,7 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def 'Update bookstore top-level container data node.'() { when: 'the bookstore top-level container is updated' def json = '{ "bookstore": { "bookstore-name": "new bookstore" }}' - objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', json, now) + objectUnderTest.updateDataNodeAndDescendants(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/', json, now, ContentType.JSON) then: 'bookstore name has been updated' def result = objectUnderTest.getDataNodes(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_ANCHOR_1, '/bookstore', DIRECT_CHILDREN_ONLY) result.leaves.'bookstore-name'[0] == 'new bookstore' @@ -459,17 +459,19 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between 2 anchors'() { when: 'attempt to get delta report between anchors' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, '/', OMIT_DESCENDANTS) + and: 'report is ordered based on xpath' + result = result.toList().sort { it.xpath } then: 'delta report contains expected number of changes' result.size() == 3 - and: 'delta report contains UPDATE action with expected xpath' - assert result[0].getAction() == 'update' + and: 'delta report contains REPLACE action with expected xpath' + assert result[0].getAction() == 'replace' assert result[0].getXpath() == '/bookstore' + and: 'delta report contains CREATE action with expected xpath' + assert result[1].getAction() == 'create' + assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" and: 'delta report contains REMOVE action with expected xpath' - assert result[1].getAction() == 'remove' - assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" - and: 'delta report contains ADD action with expected xpath' - assert result[2].getAction() == 'add' - assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" + assert result[2].getAction() == 'remove' + assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" } def 'Get delta between 2 anchors returns empty response when #scenario'() { @@ -513,11 +515,11 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { 'is empty' | "/bookstore/container-without-leaves" } - def 'Get delta between anchors for add action, where target data node #scenario'() { + def 'Get delta between anchors for "create" action, where target data node #scenario'() { when: 'attempt to get delta between leaves of data nodes present in 2 anchors' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) then: 'the expected action is present in delta report' - result.get(0).getAction() == 'add' + result.get(0).getAction() == 'create' and: 'the expected xapth is present in delta report' result.get(0).getXpath() == parentNodeXpath where: 'following data was used' @@ -531,8 +533,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between anchors when leaves of existing data nodes are updated,: #scenario'() { when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, OMIT_DESCENDANTS) - then: 'expected action is update' - assert result[0].getAction() == 'update' + then: 'expected action is "replace"' + assert result[0].getAction() == 'replace' and: 'the payload has expected leaf values' def sourceData = result[0].getSourceData() def targetData = result[0].getTargetData() @@ -548,8 +550,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def 'Get delta between anchors when child data nodes under existing parent data nodes are updated: #scenario'() { when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, sourceAnchor, targetAnchor, xpath, DIRECT_CHILDREN_ONLY) - then: 'expected action is update' - assert result[0].getAction() == 'update' + then: 'expected action is "replace"' + assert result[0].getAction() == 'replace' and: 'the delta report has expected child node xpaths' def deltaReportEntities = getDeltaReportEntities(result) def childNodeXpathsInDeltaReport = deltaReportEntities.get('xpaths') @@ -571,8 +573,8 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { when: 'attempt to get delta between leaves of existing data nodes' def result = objectUnderTest.getDeltaByDataspaceAndAnchors(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, BOOKSTORE_ANCHOR_5, parentNodeXpath, INCLUDE_ALL_DESCENDANTS) def deltaReportEntities = getDeltaReportEntities(result) - then: 'expected action is update' - assert result[0].getAction() == 'update' + then: 'expected action is "replace"' + assert result[0].getAction() == 'replace' and: 'the payload has expected parent node xpath' assert deltaReportEntities.get('xpaths').contains(parentNodeXpath) and: 'delta report has expected source and target data' @@ -591,14 +593,14 @@ class CpsDataServiceIntegrationSpec extends FunctionalSpecBase { def result = objectUnderTest.getDeltaByDataspaceAnchorAndPayload(FUNCTIONAL_TEST_DATASPACE_3, BOOKSTORE_ANCHOR_3, '/', [:], jsonPayload, OMIT_DESCENDANTS) then: 'delta report contains expected number of changes' result.size() == 3 - and: 'delta report contains UPDATE action with expected xpath' - assert result[0].getAction() == 'update' + and: 'delta report contains "replace" action with expected xpath' + assert result[0].getAction() == 'replace' assert result[0].getXpath() == '/bookstore' - and: 'delta report contains REMOVE action with expected xpath' + and: 'delta report contains "remove" action with expected xpath' assert result[1].getAction() == 'remove' assert result[1].getXpath() == "/bookstore-address[@bookstore-name='Easons-1']" - and: 'delta report contains ADD action with expected xpath' - assert result[2].getAction() == 'add' + and: 'delta report contains "create" action with expected xpath' + assert result[2].getAction() == 'create' assert result[2].getXpath() == "/bookstore-address[@bookstore-name='Crossword Bookstores']" } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataspaceServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataspaceServiceIntegrationSpec.groovy index 739e802244..d69f6cca0c 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsDataspaceServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataspaceServiceIntegrationSpec.groovy @@ -18,15 +18,15 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.cps import org.onap.cps.api.CpsDataspaceService -import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.integration.base.FunctionalSpecBase import org.onap.cps.spi.exceptions.AlreadyDefinedException import org.onap.cps.spi.exceptions.DataspaceInUseException import org.onap.cps.spi.exceptions.DataspaceNotFoundException -class CpsDataspaceServiceIntegrationSpec extends CpsIntegrationSpecBase { +class DataspaceServiceIntegrationSpec extends FunctionalSpecBase { CpsDataspaceService objectUnderTest diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy index b7b6fa11a7..9e51d80d9e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsModuleServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.cps import org.onap.cps.api.CpsModuleService import org.onap.cps.integration.base.FunctionalSpecBase @@ -31,7 +31,7 @@ import org.onap.cps.spi.exceptions.SchemaSetNotFoundException import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference -class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { +class ModuleServiceIntegrationSpec extends FunctionalSpecBase { CpsModuleService objectUnderTest @@ -134,7 +134,7 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchema1', 'newSchema2']) } - def 'Create schema set error scenario: #scenario.'() { + def 'Attempt to create schema set, error scenario: #scenario.'() { when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName' populateNewYangResourcesNameToContentMapAndAllModuleReferences(0) objectUnderTest.createSchemaSet(dataspaceName, schemaSetName, newYangResourcesNameToContentMap) @@ -146,6 +146,14 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { 'schema set already exists' | FUNCTIONAL_TEST_DATASPACE_1 | BOOKSTORE_SCHEMA_SET || AlreadyDefinedException } + def 'Attempt to create duplicate schema set from modules.'() { + when: 'attempt to store duplicate schema set from modules' + objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, newYangResourcesNameToContentMap, []) + then: 'an Already Defined Exception is thrown' + thrown(AlreadyDefinedException) + } + + /* R E A D S C H E M A S E T I N F O U S E - C A S E S */ @@ -215,7 +223,8 @@ class CpsModuleServiceIntegrationSpec extends FunctionalSpecBase { when: 'all schema sets are retrieved' def result = objectUnderTest.getSchemaSets(FUNCTIONAL_TEST_DATASPACE_1) then: 'the result contains all expected schema sets' - assert result.name == [ 'bookstoreSchemaSet', 'newSchema1' ] + assert result.name.size() == 2 + assert result.name.containsAll('bookstoreSchemaSet', 'newSchema1') cleanup: objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, ['newSchema1']) } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy index 146ea95e8b..5c2a4fc665 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/CpsQueryServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2023 Nordix Foundation + * Copyright (C) 2023-2024 Nordix Foundation * Modifications Copyright (C) 2023 TechMahindra Ltd * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); @@ -19,7 +19,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.cps import java.time.OffsetDateTime import org.onap.cps.api.CpsQueryService @@ -33,7 +33,7 @@ import static org.onap.cps.spi.FetchDescendantsOption.INCLUDE_ALL_DESCENDANTS import static org.onap.cps.spi.FetchDescendantsOption.OMIT_DESCENDANTS import static org.onap.cps.spi.PaginationOption.NO_PAGINATION -class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase { +class QueryServiceIntegrationSpec extends FunctionalSpecBase { CpsQueryService objectUnderTest @@ -382,7 +382,7 @@ class CpsQueryServiceIntegrationSpec extends FunctionalSpecBase { def result = objectUnderTest.queryDataNodesAcrossAnchors(FUNCTIONAL_TEST_DATASPACE_1, '/bookstore', OMIT_DESCENDANTS, new PaginationOption(pageIndex, pageSize)) then: 'correct bookstore names are queried' def bookstoreNames = result.collect { it.getLeaves().get('bookstore-name') } - assert bookstoreNames.toList() == expectedBookstoreNames + assert bookstoreNames.toSet() == expectedBookstoreNames.toSet() and: 'the correct number of page size is returned' assert result.size() == expectedPageSize and: 'the queried nodes have expected anchor names' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/SessionManagerIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/SessionManagerIntegrationSpec.groovy index e0a2602b23..ad153d6a4a 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/SessionManagerIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/SessionManagerIntegrationSpec.groovy @@ -18,11 +18,11 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.cps import org.onap.cps.integration.base.FunctionalSpecBase +import org.onap.cps.ri.utils.SessionManager import org.onap.cps.spi.exceptions.SessionManagerException -import org.onap.cps.spi.utils.SessionManager class SessionManagerIntegrationSpec extends FunctionalSpecBase { diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy new file mode 100644 index 0000000000..222b3c0f6f --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/AlternateIdSpec.groovy @@ -0,0 +1,54 @@ +/* + * ============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.springframework.http.HttpStatus +import org.springframework.http.MediaType + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get + +class AlternateIdSpec extends CpsIntegrationSpecBase { + + def setup() { + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'alternateId') + } + + def cleanup() { + deregisterCmHandle(DMI1_URL, 'ch-1') + } + + def 'AlternateId in pass-through data operations should return OK status.'() { + given: 'the URL for the pass-through data request' + def url = '/ncmp/v1/ch/alternateId/data/ds/ncmp-datastore:passthrough-running' + when: 'a pass-through data request is sent to NCMP' + def response = mvc.perform(get(url) + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON)) + .andReturn().response + then: 'response status is Ok' + assert response.status == HttpStatus.OK.value() + } + + + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy index 2a35313f74..a81058fbd6 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpBearerTokenPassthroughSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy @@ -18,15 +18,10 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.ncmp -import okhttp3.mockwebserver.Dispatcher -import okhttp3.mockwebserver.MockResponse -import okhttp3.mockwebserver.RecordedRequest -import org.jetbrains.annotations.NotNull import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.springframework.http.HttpHeaders -import org.springframework.http.HttpStatus import org.springframework.http.MediaType import spock.util.concurrent.PollingConditions @@ -38,31 +33,18 @@ import static org.springframework.http.HttpMethod.PUT import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { - - def lastAuthHeaderReceived = null +class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { def setup() { - dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] - registerCmHandle(DMI_URL, 'ch-1', NO_MODULE_SET_TAG) - - mockDmiServer.setDispatcher(new Dispatcher() { - @Override - MockResponse dispatch(@NotNull RecordedRequest request) throws InterruptedException { - if (request.path == '/actuator/health') { - return new MockResponse() - .addHeader("Content-Type", MediaType.APPLICATION_JSON).setBody('{"status":"UP"}') - .setResponseCode(HttpStatus.OK.value()) - } else { - lastAuthHeaderReceived = request.getHeader('Authorization') - return new MockResponse().setResponseCode(HttpStatus.OK.value()) - } - } - }) + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId['ch-2'] = ['M1', 'M3'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'alt-1') + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'alt-2') } def cleanup() { - deregisterCmHandle(DMI_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-2') } def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() { @@ -75,7 +57,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with bearer token' - lastAuthHeaderReceived == 'Bearer some-bearer-token' + assert dmiDispatcher1.lastAuthHeaderReceived == 'Bearer some-bearer-token' where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] @@ -91,7 +73,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with no authorization header' - lastAuthHeaderReceived == null + assert dmiDispatcher1.lastAuthHeaderReceived == null where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] @@ -104,7 +86,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { "operationId": "operational-1", "datastore": "ncmp-datastore:passthrough-running", "resourceIdentifier": "my-resource-id", - "targetIds": ["ch-1"] + "targetIds": ["ch-1","alt-2"] }]}""" mvc.perform(request(POST, '/ncmp/v1/data') .queryParam('topic', 'my-topic') @@ -115,7 +97,7 @@ class NcmpBearerTokenPassthroughSpec extends CpsIntegrationSpecBase { then: 'DMI will receive the async request with bearer token' new PollingConditions().within(3, () -> { - assert lastAuthHeaderReceived == 'Bearer some-bearer-token' + assert dmiDispatcher1.lastAuthHeaderReceived == 'Bearer some-bearer-token' }) } 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 new file mode 100644 index 0000000000..ffcba025e8 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy @@ -0,0 +1,248 @@ +/* + * ============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.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 +import org.onap.cps.ncmp.api.NcmpResponseStatus +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle +import org.onap.cps.ncmp.events.lcm.v1.LcmEvent +import org.onap.cps.ncmp.impl.inventory.models.CmHandleState +import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import spock.util.concurrent.PollingConditions + +import java.time.Duration + +class CmHandleCreateSpec extends CpsIntegrationSpecBase { + + NetworkCmProxyInventoryFacade objectUnderTest + def uniqueId = 'ch-unique-id-for-create-test' + + static KafkaConsumer kafkaConsumer + + def setup() { + objectUnderTest = networkCmProxyInventoryFacade + subscribeAndClearPreviousMessages() + } + + def cleanupSpec() { + kafkaConsumer.unsubscribe() + kafkaConsumer.close() + } + + def 'CM Handle registration.'() { + given: 'DMI will return modules when requested' + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId[uniqueId] = ['M1', 'M2'] + + when: 'a CM-handle is registered for creation' + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: uniqueId) + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) + + then: 'registration gives successful response' + assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(uniqueId)] + + and: 'CM-handle is initially in ADVISED state' + assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState + + then: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() + + then: 'CM-handle goes to READY state after module sync' + new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState + }) + + and: 'the CM-handle has expected modules' + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(uniqueId).moduleName.sort() + + then: 'get the latest messages' + def consumerRecords = getLatestConsumerRecords() + + 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, uniqueId) + } + + def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() { + given: 'DMI is not available to handle requests' + dmiDispatcher1.isAvailable = false + + 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.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, () -> { + def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState('ch-1') + assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED + assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_SYNC_FAILED + }) + + and: 'CM-handle has no modules' + assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty + + cleanup: 'deregister CM handle' + deregisterCmHandle(DMI1_URL, 'ch-1') + } + + def 'Create a CM-handle with existing moduleSetTag.'() { + given: 'DMI will return modules when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']] + and: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"' + registerCmHandle(DMI1_URL, 'ch-1', 'A') + registerCmHandle(DMI1_URL, 'ch-2', 'B') + + when: 'a CM-handle is registered for creation with moduleSetTag "B"' + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B') + 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, () -> { + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-3').cmHandleState + }) + + and: 'the CM-handle has expected moduleSetTag' + assert objectUnderTest.getNcmpServiceCmHandle('ch-3').moduleSetTag == 'B' + + and: 'the CM-handle has expected modules from module set "B": M1 and M3' + assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort() + + cleanup: 'deregister CM handles' + deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2', 'ch-3']) + } + + def 'Create CM-handles with alternate IDs.'() { + given: 'DMI will return modules for all CM-handles when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = (1..7).collectEntries{ ['ch-'+it, ['M1']] } + and: 'an existing CM-handle with an alternate ID' + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'existing-alt-id') + and: 'an existing CM-handle with no alternate ID' + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, NO_ALTERNATE_ID) + + when: 'a batch of CM-handles is registered for creation with various alternate IDs' + def cmHandlesToCreate = [ + new NcmpServiceCmHandle(cmHandleId: 'ch-3', alternateId: NO_ALTERNATE_ID), + new NcmpServiceCmHandle(cmHandleId: 'ch-4', alternateId: 'unique-alt-id'), + new NcmpServiceCmHandle(cmHandleId: 'ch-5', alternateId: 'existing-alt-id'), + new NcmpServiceCmHandle(cmHandleId: 'ch-6', alternateId: 'duplicate-alt-id'), + new NcmpServiceCmHandle(cmHandleId: 'ch-7', alternateId: 'duplicate-alt-id'), + ] + def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration) + + then: 'registration gives expected responses' + assert dmiPluginRegistrationResponse.createdCmHandles.sort { it.cmHandle } == [ + CmHandleRegistrationResponse.createSuccessResponse('ch-3'), + CmHandleRegistrationResponse.createSuccessResponse('ch-4'), + CmHandleRegistrationResponse.createFailureResponse('ch-5', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED), + CmHandleRegistrationResponse.createSuccessResponse('ch-6'), + CmHandleRegistrationResponse.createFailureResponse('ch-7', NcmpResponseStatus.ALTERNATE_ID_ALREADY_ASSOCIATED), + ] + + cleanup: 'deregister CM handles' + deregisterCmHandles(DMI1_URL, (1..7).collect{ 'ch-'+it }) + } + + def 'CM Handle retry after failed module sync.'() { + given: 'DMI is not initially available to handle requests' + dmiDispatcher1.isAvailable = false + + 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.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 + }) + + when: 'DMI is available for retry' + 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 -> + assert objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState == CmHandleState.READY + } + }) + + and: 'Both CM-handles have expected modules' + ['ch-1', 'ch-2'].each { cmHandleId -> + assert objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() == ['M1', 'M2'] + } + + 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 + } + return 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 new file mode 100644 index 0000000000..67011f811b --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpdateSpec.groovy @@ -0,0 +1,89 @@ +/* + * ============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.api.NcmpResponseStatus +import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade +import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse +import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle + +class CmHandleUpdateSpec extends CpsIntegrationSpecBase { + + NetworkCmProxyInventoryFacade objectUnderTest + + def setup() { + objectUnderTest = networkCmProxyInventoryFacade + } + + def 'Update of CM-handle with new or unchanged alternate ID succeeds.'() { + given: 'DMI will return modules when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']] + and: "existing CM-handle with alternate ID: $oldAlternateId" + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, oldAlternateId) + + when: "CM-handle is registered for update with new alternate ID: $newAlternateId" + def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: newAlternateId) + def dmiPluginRegistrationResponse = + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, updatedCmHandles: [cmHandleToUpdate])) + + then: 'registration gives successful response' + assert dmiPluginRegistrationResponse.updatedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')] + + and: 'the CM-handle has expected alternate ID' + assert objectUnderTest.getNcmpServiceCmHandle('ch-1').alternateId == expectedAlternateId + + cleanup: 'deregister CM handles' + deregisterCmHandle(DMI1_URL, 'ch-1') + + where: + oldAlternateId | newAlternateId || expectedAlternateId + '' | '' || '' + '' | 'new' || 'new' + 'old' | 'old' || 'old' + 'old' | null || 'old' + 'old' | '' || 'old' + 'old' | ' ' || 'old' + } + + def 'Update of CM-handle with previously set alternate ID fails.'() { + given: 'DMI will return modules when requested' + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']] + and: 'existing CM-handle with alternate ID' + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'original') + + when: 'a CM-handle is registered for update with new alternate ID' + def cmHandleToUpdate = new NcmpServiceCmHandle(cmHandleId: 'ch-1', alternateId: 'new') + def dmiPluginRegistrationResponse = + 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)] + + and: 'the CM-handle still has the old alternate ID' + assert objectUnderTest.getNcmpServiceCmHandle('ch-1').alternateId == 'original' + + cleanup: 'deregister CM handles' + deregisterCmHandle(DMI1_URL, 'ch-1') + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy index 72e798335a..a5e3daf289 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.ncmp import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.onap.cps.ncmp.api.inventory.NetworkCmProxyInventoryFacade @@ -29,58 +29,59 @@ import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import spock.util.concurrent.PollingConditions -class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { +class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { NetworkCmProxyInventoryFacade objectUnderTest - static final CM_HANDLE_ID = 'ch-1' - static final CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG = 'ch-2' + def cmHandleId = 'ch-1' + def cmHandleIdWithExistingModuleSetTag = 'ch-2' def setup() { objectUnderTest = networkCmProxyInventoryFacade + moduleSyncService.clearPrivateModuleSetCache() } def 'Upgrade CM-handle with new moduleSetTag or no moduleSetTag.'() { given: 'a CM-handle is created with expected initial modules: M1 and M2' - dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] - registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag) - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() + dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleId] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, cmHandleId, initialModuleSetTag) + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() when: "the CM-handle is upgraded with given moduleSetTag '${updatedModuleSetTag}'" - def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule( - new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) + def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [cmHandleId], moduleSetTag: updatedModuleSetTag) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' - assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)] + assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(cmHandleId)] and: 'CM-handle is in LOCKED state due to MODULE_UPGRADE' - def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID) + def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(cmHandleId) assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE assert cmHandleCompositeState.lockReason.details == "Upgrade to ModuleSetTag: ${updatedModuleSetTag}" when: 'DMI will return different modules for upgrade: M1 and M3' - dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3'] - and: 'module sync runs' - moduleSyncWatchdog.resetPreviouslyFailedCmHandles() - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() + dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleId] = ['M1', 'M3'] + + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } then: 'CM-handle goes to READY state' - new PollingConditions().eventually { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState - } + new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState + }) and: 'the CM-handle has expected moduleSetTag' - assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag + assert objectUnderTest.getNcmpServiceCmHandle(cmHandleId).moduleSetTag == updatedModuleSetTag and: 'CM-handle has expected updated modules: M1 and M3' - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() + assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() cleanup: 'deregister CM-handle' - deregisterCmHandle(DMI_URL, CM_HANDLE_ID) + deregisterCmHandle(DMI1_URL, cmHandleId) - where: + where: 'following module set tags are used' initialModuleSetTag | updatedModuleSetTag NO_MODULE_SET_TAG | NO_MODULE_SET_TAG NO_MODULE_SET_TAG | 'new' @@ -90,40 +91,39 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { def 'Upgrade CM-handle with existing moduleSetTag.'() { given: 'DMI will return modules for registration' - dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] - dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG] = ['M1', 'M3'] + dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleId] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleIdWithExistingModuleSetTag] = ['M1', 'M3'] and: "an existing CM-handle handle with moduleSetTag '${updatedModuleSetTag}'" - registerCmHandle(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag) - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG).moduleName.sort() + registerCmHandle(DMI1_URL, cmHandleIdWithExistingModuleSetTag, updatedModuleSetTag) + assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleIdWithExistingModuleSetTag).moduleName.sort() and: "a CM-handle with moduleSetTag '${initialModuleSetTag}' which will be upgraded" - registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag) - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() + registerCmHandle(DMI1_URL, cmHandleId, initialModuleSetTag) + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() when: "CM-handle is upgraded to moduleSetTag '${updatedModuleSetTag}'" - def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: updatedModuleSetTag) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule( - new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) + def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [cmHandleId], moduleSetTag: updatedModuleSetTag) + def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' - assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)] + assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(cmHandleId)] - when: 'module sync runs' - moduleSyncWatchdog.resetPreviouslyFailedCmHandles() - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } - then: 'CM-handle goes to READY state' - new PollingConditions().eventually { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState - } + and: 'CM-handle goes to READY state' + new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState + }) and: 'the CM-handle has expected moduleSetTag' - assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == updatedModuleSetTag + assert objectUnderTest.getNcmpServiceCmHandle(cmHandleId).moduleSetTag == updatedModuleSetTag and: 'CM-handle has expected updated modules: M1 and M3' - assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() + assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() cleanup: 'deregister CM-handle' - deregisterCmHandles(DMI_URL, [CM_HANDLE_ID, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG]) + deregisterCmHandles(DMI1_URL, [cmHandleId, cmHandleIdWithExistingModuleSetTag]) where: initialModuleSetTag | updatedModuleSetTag @@ -133,56 +133,55 @@ class NcmpCmHandleUpgradeSpec extends CpsIntegrationSpecBase { def 'Skip upgrade of CM-handle with same moduleSetTag as before.'() { given: 'an existing CM-handle with expected initial modules: M1 and M2' - dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] - registerCmHandle(DMI_URL, CM_HANDLE_ID, 'same') - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() + dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleId] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, cmHandleId, 'same') + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() when: 'CM-handle is upgraded with the same moduleSetTag' - def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'same') - objectUnderTest.updateDmiRegistrationAndSyncModule( - new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) + def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [cmHandleId], moduleSetTag: 'same') + objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'CM-handle remains in READY state' - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState and: 'the CM-handle has same moduleSetTag as before' - assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'same' + assert objectUnderTest.getNcmpServiceCmHandle(cmHandleId).moduleSetTag == 'same' then: 'CM-handle has same modules as before: M1 and M2' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() cleanup: 'deregister CM-handle' - deregisterCmHandle(DMI_URL, CM_HANDLE_ID) + deregisterCmHandle(DMI1_URL, cmHandleId) } def 'Upgrade of CM-handle fails due to DMI error.'() { given: 'a CM-handle exists' - dmiDispatcher.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] - registerCmHandle(DMI_URL, CM_HANDLE_ID, 'oldTag') + dmiDispatcher1.moduleNamesPerCmHandleId[cmHandleId] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, cmHandleId, 'oldTag') and: 'DMI is not available for upgrade' - dmiDispatcher.isAvailable = false + dmiDispatcher1.isAvailable = false when: 'the CM-handle is upgraded' - def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [CM_HANDLE_ID], moduleSetTag: 'newTag') - objectUnderTest.updateDmiRegistrationAndSyncModule( - new DmiPluginRegistration(dmiPlugin: DMI_URL, upgradedCmHandles: cmHandlesToUpgrade)) + def cmHandlesToUpgrade = new UpgradedCmHandles(cmHandles: [cmHandleId], moduleSetTag: 'newTag') + objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) - and: 'module sync runs' - moduleSyncWatchdog.resetPreviouslyFailedCmHandles() - moduleSyncWatchdog.moduleSyncAdvisedCmHandles() + 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(timeout: 3).eventually { - def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID) + new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { + def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState(cmHandleId) assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_UPGRADE_FAILED - } + }) and: 'the CM-handle has same moduleSetTag as before' - assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'oldTag' + assert objectUnderTest.getNcmpServiceCmHandle(cmHandleId).moduleSetTag == 'oldTag' cleanup: 'deregister CM-handle' - deregisterCmHandle(DMI_URL, CM_HANDLE_ID) + deregisterCmHandle(DMI1_URL, cmHandleId) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmNotificationSubscriptionSpec.groovy index c2768f1942..a5f7d08c58 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpCmNotificationSubscriptionSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmNotificationSubscriptionSpec.groovy @@ -18,34 +18,33 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.ncmp import org.onap.cps.integration.base.CpsIntegrationSpecBase -import org.onap.cps.ncmp.api.impl.events.cmsubscription.service.CmNotificationSubscriptionPersistenceService +import org.onap.cps.ncmp.impl.cmnotificationsubscription.utils.CmSubscriptionPersistenceService import org.springframework.beans.factory.annotation.Autowired import static org.onap.cps.ncmp.api.data.models.DatastoreType.PASSTHROUGH_RUNNING -class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { +class CmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { @Autowired - CmNotificationSubscriptionPersistenceService cmNotificationSubscriptionPersistenceService; + CmSubscriptionPersistenceService cmSubscriptionPersistenceService def 'Adding a new cm notification subscription'() { given: 'there is no ongoing cm subscription for the following' def datastoreType = PASSTHROUGH_RUNNING def cmHandleId = 'ch-1' def xpath = '/x/y' - assert cmNotificationSubscriptionPersistenceService. - getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 0 + assert cmSubscriptionPersistenceService. + getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() == 0 when: 'we add a new cm notification subscription' - cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType,cmHandleId,xpath, + cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandleId, xpath, 'subId-1') then: 'there is an ongoing cm subscription for that CM handle and xpath' - assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,xpath) + assert cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, xpath) and: 'only one subscription id is related to now ongoing cm subscription' - assert cmNotificationSubscriptionPersistenceService. - getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 1 + assert cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() == 1 } def 'Adding a cm notification subscription to the already existing cm handle but non existing xpath'() { @@ -53,18 +52,17 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { def datastoreType = PASSTHROUGH_RUNNING def cmHandleId = 'ch-1' def existingXpath = '/x/y' - assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,existingXpath) + assert cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, existingXpath) and: 'a non existing cm subscription with same datastore name and cm handle but different xpath' def nonExistingXpath = '/x2/y2' - assert !cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,nonExistingXpath) + assert !cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, nonExistingXpath) when: 'a new cm notification subscription is made for the existing cm handle and non existing xpath' - cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType,cmHandleId, nonExistingXpath, + cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandleId, nonExistingXpath, 'subId-2') then: 'there is an ongoing cm subscription for that CM handle and xpath' - assert cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType,cmHandleId,nonExistingXpath) + assert cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, nonExistingXpath) and: 'only one subscription id is related to now ongoing cm subscription' - assert cmNotificationSubscriptionPersistenceService. - getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,nonExistingXpath).size() == 1 + assert cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, nonExistingXpath).size() == 1 } def 'Adding a cm notification subscription to the already existing cm handle and xpath'() { @@ -73,10 +71,10 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { def cmHandleId = 'ch-1' def xpath = '/x/y' when: 'a new cm notification subscription is made for the SAME CM handle and xpath' - cmNotificationSubscriptionPersistenceService.addCmNotificationSubscription(datastoreType,cmHandleId,xpath, + cmSubscriptionPersistenceService.addCmSubscription(datastoreType, cmHandleId, xpath, 'subId-3') then: 'it is added to the ongoing list of subscription ids' - def subscriptionIds = cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath) + def subscriptionIds = cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath) assert subscriptionIds.size() == 2 and: 'both subscription ids exists for the CM handle and xpath' assert subscriptionIds.contains("subId-1") && subscriptionIds.contains("subId-3") @@ -89,13 +87,12 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { def xpath = '/x/y' and: 'the number of subscribers is as follows' def originalNumberOfSubscribers = - cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() + cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() when: 'a subscriber is removed' - cmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(datastoreType,cmHandleId,xpath,'subId-3') + cmSubscriptionPersistenceService.removeCmSubscription(datastoreType, cmHandleId, xpath, 'subId-3') then: 'the number of subscribers is reduced by 1' - def updatedNumberOfSubscribers = - cmNotificationSubscriptionPersistenceService.getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() - assert updatedNumberOfSubscribers == originalNumberOfSubscribers-1 + def updatedNumberOfSubscribers = cmSubscriptionPersistenceService.getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() + assert updatedNumberOfSubscribers == originalNumberOfSubscribers - 1 } def 'Removing the LAST cm notification subscriber for a given cm handle, datastore and xpath'() { @@ -104,12 +101,12 @@ class NcmpCmNotificationSubscriptionSpec extends CpsIntegrationSpecBase { def cmHandleId = 'ch-1' def xpath = '/x/y' and: 'there is only one subscriber' - assert cmNotificationSubscriptionPersistenceService - .getOngoingCmNotificationSubscriptionIds(datastoreType,cmHandleId,xpath).size() == 1 + assert cmSubscriptionPersistenceService + .getOngoingCmSubscriptionIds(datastoreType, cmHandleId, xpath).size() == 1 when: 'only subscriber is removed' - cmNotificationSubscriptionPersistenceService.removeCmNotificationSubscription(datastoreType,cmHandleId,xpath,'subId-1') + cmSubscriptionPersistenceService.removeCmSubscription(datastoreType, cmHandleId, xpath, 'subId-1') then: 'there are no longer any subscriptions for the cm handle, datastore and xpath' - assert !cmNotificationSubscriptionPersistenceService.isOngoingCmNotificationSubscription(datastoreType, cmHandleId, xpath) + assert !cmSubscriptionPersistenceService.isOngoingCmSubscription(datastoreType, cmHandleId, xpath) } } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy new file mode 100644 index 0000000000..4d04eeeb81 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobResultServiceSpec.groovy @@ -0,0 +1,44 @@ +/* + * ============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.api.datajobs.DataJobResultService +import org.springframework.beans.factory.annotation.Autowired + +class DataJobResultServiceSpec extends CpsIntegrationSpecBase { + + @Autowired + DataJobResultService dataJobResultService; + + def 'Get the status of a data job from DMI.'() { + given: 'the required data about the data job' + def authorization = 'my authorization header' + def dmiServiceName = DMI1_URL + def dataProducerId = 'some-data-producer-id' + def dataProducerJobId = 'some-data-producer-job-id' + def destination = 'some-destination' + when: 'the data job status checked' + def result = dataJobResultService.getDataJobResult(authorization, dmiServiceName, dataProducerId, dataProducerJobId, destination) + then: 'the status is that defined in the mock service.' + assert result == '{ "result": "some result"}' + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy new file mode 100644 index 0000000000..6e5c0e40c2 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DataJobStatusServiceSpec.groovy @@ -0,0 +1,23 @@ +package org.onap.cps.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.api.datajobs.DataJobStatusService +import org.springframework.beans.factory.annotation.Autowired + +class DataJobStatusServiceSpec extends CpsIntegrationSpecBase { + + @Autowired + DataJobStatusService dataJobStatusService + + def 'Get the status of a data job from DMI.'() { + given: 'the required data about the data job' + def dmiServiceName = DMI1_URL + def dataProducerId = 'some-data-producer-id' + def dataProducerJobId = 'some-data-producer-job-id' + def authorization = 'my authorization header' + when: 'the data job status checked' + def result = dataJobStatusService.getDataJobStatus(authorization, dmiServiceName, dataProducerId, dataProducerJobId) + then: 'the status is that defined in the mock service.' + assert result == 'status details from mock service' + } +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DmiUrlEncodingPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DmiUrlEncodingPassthroughSpec.groovy new file mode 100644 index 0000000000..4e9b809eff --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/DmiUrlEncodingPassthroughSpec.groovy @@ -0,0 +1,65 @@ +/* + * ============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.springframework.http.MediaType + +import static org.springframework.http.HttpMethod.DELETE +import static org.springframework.http.HttpMethod.GET +import static org.springframework.http.HttpMethod.PATCH +import static org.springframework.http.HttpMethod.POST +import static org.springframework.http.HttpMethod.PUT +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +class DmiUrlEncodingPassthroughSpec extends CpsIntegrationSpecBase { + + def setup() { + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG) + } + + def cleanup() { + deregisterCmHandle(DMI1_URL, 'ch-1') + } + + def 'DMI URL encoding for pass-through operational data operations with GET request'() { + when: 'sending a GET pass-through data request to NCMP' + mvc.perform(request(GET, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-operational') + .queryParam('resourceIdentifier', 'parent/child') + .queryParam('options', '(a=1,b=2)')) + .andExpect(status().is2xxSuccessful()) + then: 'verify that DMI received the request with the correctly encoded URL' + assert dmiDispatcher1.dmiResourceDataUrl == '/dmi/v1/ch/ch-1/data/ds/ncmp-datastore%3Apassthrough-operational?resourceIdentifier=parent%2Fchild&options=%28a%3D1%2Cb%3D2%29' + } + + def 'DMI URL encoding for pass-through running data operations with POST request'() { + when: 'sending a pass-through data request to NCMP with various HTTP methods' + mvc.perform(request(POST, '/ncmp/v1/ch/ch-1/data/ds/ncmp-datastore:passthrough-running') + .queryParam('resourceIdentifier', 'parent/child') + .contentType(MediaType.APPLICATION_JSON) + .content('{ "some-json": "data" }')) + .andExpect(status().is2xxSuccessful()) + then: 'verify that DMI received the request with the correctly encoded URL' + assert dmiDispatcher1.dmiResourceDataUrl == '/dmi/v1/ch/ch-1/data/ds/ncmp-datastore%3Apassthrough-running?resourceIdentifier=parent%2Fchild' + } +} 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..963bc1fe61 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy @@ -0,0 +1,156 @@ +/* + * ============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 io.micrometer.core.instrument.MeterRegistry +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog +import org.springframework.beans.factory.annotation.Autowired +import spock.util.concurrent.PollingConditions + +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase { + + ModuleSyncWatchdog objectUnderTest + + @Autowired + MeterRegistry meterRegistry + + def executorService = Executors.newFixedThreadPool(2) + def PARALLEL_SYNC_SAMPLE_SIZE = 100 + + def setup() { + objectUnderTest = moduleSyncWatchdog + } + + def cleanup() { + try { + deregisterSequenceOfCmHandles(DMI1_URL, PARALLEL_SYNC_SAMPLE_SIZE, 1) + moduleSyncWorkQueue.clear() + } finally { + executorService.shutdownNow() + } + } + + def 'Watchdog is disabled for test.'() { + given: + registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, PARALLEL_SYNC_SAMPLE_SIZE, 1) + 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 'CPS-2478 Highlight module sync inefficiencies.'() { + given: 'register 250 cm handles with module set tag cps-2478-A' + def numberOfTags = 2 + def cmHandlesPerTag = 250 + def totalCmHandles = numberOfTags * cmHandlesPerTag + def offset = 1 + registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, 'cps-2478-A', cmHandlesPerTag, offset) + and: 'register anther 250 cm handles with module set tag cps-2478-B' + offset += cmHandlesPerTag + registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, 'cps-2478-B', cmHandlesPerTag, offset) + and: 'clear any previous instrumentation' + meterRegistry.clear() + when: 'sync all advised cm handles' + objectUnderTest.moduleSyncAdvisedCmHandles() + Thread.sleep(100) + then: 'retry until all schema sets are stored in db (1 schema set for each cm handle)' + def dbSchemaSetStorageTimer = meterRegistry.get('cps.module.persistence.schemaset.store').timer() + new PollingConditions().within(10, () -> { + objectUnderTest.moduleSyncAdvisedCmHandles() + Thread.sleep(100) + assert dbSchemaSetStorageTimer.count() >= 500 + }) + then: 'wait till at least 5 batches of state updates are done (often more because of retries of locked cm handles)' + def dbStateUpdateTimer = meterRegistry.get('cps.ncmp.cmhandle.state.update.batch').timer() + new PollingConditions().within(10, () -> { + assert dbStateUpdateTimer.count() >= 5 + }) + and: 'the db has been queried for tags exactly 2 times.' + def dbModuleQueriesTimer = meterRegistry.get('cps.module.service.module.reference.query.by.attribute').timer() + assert dbModuleQueriesTimer.count() == 2 + and: 'exactly 2 calls to DMI to get module references' + def dmiModuleRetrievalTimer = meterRegistry.get('cps.ncmp.inventory.module.references.from.dmi').timer() + assert dmiModuleRetrievalTimer.count() == 2 + and: 'log the relevant instrumentation' + logInstrumentation(dbModuleQueriesTimer, 'query module references') + logInstrumentation(dmiModuleRetrievalTimer, 'get modules from DMI ') + logInstrumentation(dbSchemaSetStorageTimer, 'store schema sets ') + logInstrumentation(dbStateUpdateTimer, 'batch state updates ') + cleanup: 'remove all cm handles' + deregisterSequenceOfCmHandles(DMI1_URL, totalCmHandles, 1) + } + + 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' + registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, PARALLEL_SYNC_SAMPLE_SIZE, 1) + 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() == PARALLEL_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' + registerSequenceOfCmHandlesWithManyModuleReferencesButDoNotWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, PARALLEL_SYNC_SAMPLE_SIZE, 1) + 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() == PARALLEL_SYNC_SAMPLE_SIZE + } + + def logInstrumentation(timer, description) { + System.out.println('*** CPS-2478, ' + description + ' : ' + timer.count()+ ' times, total ' + timer.totalTime(TimeUnit.MILLISECONDS) + ' ms') + return true + } + + 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/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy new file mode 100644 index 0000000000..f897393a53 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy @@ -0,0 +1,72 @@ +/* + * ============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 com.fasterxml.jackson.databind.ObjectMapper +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType + +import static org.springframework.http.HttpMethod.POST +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request + +class PolicyExecutorIntegrationSpec extends CpsIntegrationSpecBase { + + def objectMapper = new ObjectMapper() + + def setup() { + // Enable mocked policy executor logic + policyDispatcher.allowAll = false; + //minimum setup for cm handles with alternate ids + dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': [], 'ch-2': [], 'ch-3':[]] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'fdn1') + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'fdn2') + registerCmHandle(DMI1_URL, 'ch-3', NO_MODULE_SET_TAG, 'mock slow response') + } + + def cleanup() { + deregisterSequenceOfCmHandles(DMI1_URL, 3, 1) + } + + def 'Policy Executor create request with #scenario.'() { + when: 'a pass-through write request is sent to NCMP' + def response = mvc.perform(request(POST, "/ncmp/v1/ch/$cmHandle/data/ds/ncmp-datastore:passthrough-running") + .queryParam('resourceIdentifier', 'my-resource-id') + .contentType(MediaType.APPLICATION_JSON) + .content('{ "some-json": "data" }') + .header(HttpHeaders.AUTHORIZATION, authorization)) + .andReturn().response + then: 'the expected status code is returned' + response.getStatus() == execpectedStatusCode + and: 'when not allowed the response body contains the expected message' + if (expectedMessage!='allow') { + def bodyAsMap = objectMapper.readValue(response.getContentAsByteArray(), Map.class) + assert bodyAsMap.get('message').endsWith(expectedMessage) + } + where: 'following parameters are used' + scenario | cmHandle | authorization || execpectedStatusCode || expectedMessage + 'accepted cm handle' | 'ch-1' | 'mock expects "ABC"' || 201 || 'allow' + 'un-accepted cm handle' | 'ch-2' | 'mock expects "ABC"' || 409 || 'deny from mock server (dispatcher)' + 'timeout' | 'ch-3' | 'mock expects "ABC"' || 409 || 'test default decision' + 'invalid authorization' | 'ch-1' | 'something else' || 409 || 'test default decision' + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy index 5325f1a86e..7ce3cf5e17 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/NcmpRestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy @@ -18,7 +18,7 @@ * ============LICENSE_END========================================================= */ -package org.onap.cps.integration.functional +package org.onap.cps.integration.functional.ncmp import static org.hamcrest.Matchers.containsInAnyOrder import static org.hamcrest.Matchers.hasSize @@ -31,29 +31,29 @@ import org.onap.cps.integration.base.CpsIntegrationSpecBase import org.springframework.http.MediaType import spock.util.concurrent.PollingConditions -class NcmpRestApiSpec extends CpsIntegrationSpecBase { +class RestApiSpec extends CpsIntegrationSpecBase { def 'Register CM Handles using REST API.'() { given: 'DMI will return modules' - dmiDispatcher.moduleNamesPerCmHandleId = [ + dmiDispatcher1.moduleNamesPerCmHandleId = [ 'ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M2'], 'ch-3': ['M1', 'M3'] ] - and: 'a POST request is made to register the CM Handles' - def requestBody = '{"dmiPlugin":"'+DMI_URL+'","createdCmHandles":[{"cmHandle":"ch-1"},{"cmHandle":"ch-2"},{"cmHandle":"ch-3"}]}' + when: 'a POST request is made to register the CM Handles' + 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()) - when: 'module sync runs' + and: 'the module sync watchdog is triggered' moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' - new PollingConditions().eventually { + new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { (1..3).each { mvc.perform(get('/ncmp/v1/ch/ch-'+it)) .andExpect(status().isOk()) .andExpect(jsonPath('$.state.cmHandleState').value('READY')) } - } + }) } def 'Search for CM Handles by module using REST API.'() { @@ -67,20 +67,46 @@ class NcmpRestApiSpec 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(expectedCmHandleReferences.toArray()))) + .andExpect(jsonPath('$', hasSize(expectedCmHandleReferences.size()))); + where: + 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.'() { + given: 'a JSON request body containing search parameter' + def requestBodyWithSearchCondition = """{ + "cmHandleQueryParameters": [ + { + "conditionName": "cmHandleWithCpsPath", + "conditionParameters": [ {"cpsPath" : "%s"} ] + } + ] + }""".formatted(cpsPath) + expect: "a search for cps path ${cpsPath} returns expected CM handles" + mvc.perform(post('/ncmp/v1/ch/id-searches').contentType(MediaType.APPLICATION_JSON).content(requestBodyWithSearchCondition)) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath('$[*]', containsInAnyOrder(expectedCmHandles.toArray()))) .andExpect(jsonPath('$', hasSize(expectedCmHandles.size()))); where: - moduleName || expectedCmHandles - 'M1' || ['ch-1', 'ch-2', 'ch-3'] - 'M2' || ['ch-1', 'ch-2'] - 'M3' || ['ch-3'] + scenario | cpsPath || expectedCmHandles + 'All Ready CM handles' | "//state[@cm-handle-state='READY']" || ['ch-1', 'ch-2', 'ch-3'] + 'Having Alternate ID alt-3' | "//cm-handles[@alternate-id='alt-3']" || ['ch-3'] } def 'De-register CM handles using REST API.'() { when: 'a POST request is made to deregister the CM Handle' - def requestBody = '{"dmiPlugin":"'+DMI_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}' + def requestBody = '{"dmiPlugin":"'+DMI1_URL+'", "removedCmHandles": ["ch-1", "ch-2", "ch-3"]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) .andExpect(status().is2xxSuccessful()) then: 'the CM handles are not found using GET' diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy new file mode 100644 index 0000000000..834e1399e3 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/WriteSubJobSpec.groovy @@ -0,0 +1,75 @@ +/* + * ============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.api.datajobs.DataJobService +import org.onap.cps.ncmp.api.datajobs.models.DataJobMetadata +import org.onap.cps.ncmp.api.datajobs.models.DataJobWriteRequest +import org.onap.cps.ncmp.api.datajobs.models.SubJobWriteResponse +import org.onap.cps.ncmp.api.datajobs.models.WriteOperation +import org.springframework.beans.factory.annotation.Autowired + +class WriteSubJobSpec extends CpsIntegrationSpecBase { + + @Autowired + DataJobService dataJobService + + def setup() { + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1'] + dmiDispatcher1.moduleNamesPerCmHandleId['ch-2'] = ['M2'] + dmiDispatcher2.moduleNamesPerCmHandleId['ch-3'] = ['M3'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'p1') + registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, 'p2') + registerCmHandle(DMI2_URL, 'ch-3', NO_MODULE_SET_TAG, 'p3') + } + + def cleanup() { + deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-2') + deregisterCmHandle(DMI2_URL, 'ch-3') + } + + def 'Create a sub-job write request.'() { + given: 'the required input data for the write job' + def authorization = 'my authorization header' + def dataJobWriteRequest = new DataJobWriteRequest([new WriteOperation('p1', '', '', null), new WriteOperation('p2', '', '', null), new WriteOperation('p3', '', '', null)]) + def myDataJobMetadata = new DataJobMetadata('d1', '', '') + def dataJobId = 'my-data-job-id' + when: 'sending a write job to NCMP with 2 sub-jobs for DMI 1 and 1 sub-job for DMI 2' + def response = dataJobService.writeDataJob(authorization, dataJobId, myDataJobMetadata, dataJobWriteRequest) + then: 'each DMI received the expected sub-jobs and the response has the expected values' + assert response.size() == 2 + assert response[0].class == SubJobWriteResponse.class + assert response[0].subJobId == "some sub job id" + assert response[0].dmiServiceName == "some dmi service name" + assert response[0].dataProducerId == "some data producer id" + and: 'dmi 1 received the correct job details' + def receivedSubJobsForDispatcher1 = dmiDispatcher1.receivedSubJobs['?destination=d1']['data'].collect() + assert receivedSubJobsForDispatcher1.size() == 2 + assert receivedSubJobsForDispatcher1[0]['path'] == 'p1' + assert receivedSubJobsForDispatcher1[1]['path'] == 'p2' + and: 'dmi 2 received the correct job details' + def receivedSubJobsForDispatcher2 = dmiDispatcher2.receivedSubJobs['?destination=d1']['data'].collect() + assert receivedSubJobsForDispatcher2.size() == 1 + assert receivedSubJobsForDispatcher2[0]['path'] == 'p3' + } +} 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 0ca200211a..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 @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2023-2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -22,14 +23,17 @@ package org.onap.cps.integration.performance.base import org.onap.cps.integration.ResourceMeter import org.onap.cps.spi.FetchDescendantsOption +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' @@ -68,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) + 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) + cpsDataService.saveListElements(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, REGISTRY_PARENT, data, now, ContentType.JSON) } } @@ -115,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/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy index 7bcec968e5..03abdb4b3f 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/UpdatePerfTest.groovy @@ -50,7 +50,7 @@ class UpdatePerfTest extends CpsPerfTestBase { given: 'replacement JSON for node containing list of device nodes' def jsonData = '{ "openroadm-devices": ' + generateJsonForOpenRoadmDevices(startId, totalNodes, changeLeaves) + '}' when: 'the container node is updated' - objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now) + objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now, ContentType.JSON) then: 'there are the expected number of total nodes' assert totalNodes == countDataNodes('/openroadm-devices/openroadm-device') where: @@ -68,7 +68,7 @@ class UpdatePerfTest extends CpsPerfTestBase { def jsonData = '{ "openroadm-devices": ' + generateJsonForOpenRoadmDevices(startId, totalNodes, changeLeaves) + '}' when: 'the container node is updated' resourceMeter.start() - objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now) + objectUnderTest.updateDataNodeAndDescendants(CPS_PERFORMANCE_TEST_DATASPACE, UPDATE_TEST_ANCHOR, '/', jsonData, now, ContentType.JSON) resourceMeter.stop() then: 'there are the expected number of total nodes' assert totalNodes == countDataNodes('/openroadm-devices/openroadm-device') diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy index 0195611740..9f6c78d5f5 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/performance/cps/WritePerfTest.groovy @@ -1,6 +1,7 @@ /* * ============LICENSE_START======================================================= * Copyright (C) 2023-2024 Nordix Foundation + * Modifications Copyright (C) 2024 TechMahindra Ltd. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. @@ -20,6 +21,8 @@ package org.onap.cps.integration.performance.cps +import org.onap.cps.utils.ContentType + import java.time.OffsetDateTime import org.onap.cps.integration.performance.base.CpsPerfTestBase @@ -87,7 +90,7 @@ class WritePerfTest extends CpsPerfTestBase { ']}' when: 'device nodes are added' resourceMeter.start() - cpsDataService.saveListElements(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR, '/openroadm-devices', jsonListData, OffsetDateTime.now()) + cpsDataService.saveListElements(CPS_PERFORMANCE_TEST_DATASPACE, WRITE_TEST_ANCHOR, '/openroadm-devices', jsonListData, OffsetDateTime.now(), ContentType.JSON) resourceMeter.stop() then: 'the operation takes less than #expectedDuration and memory used is within limit' recordAndAssertResourceUsage("Saving list of ${totalNodes} devices", diff --git a/integration-test/src/test/java/org/onap/cps/integration/DmiStubTestContainer.java b/integration-test/src/test/java/org/onap/cps/integration/DmiStubTestContainer.java new file mode 100644 index 0000000000..1bffb35c14 --- /dev/null +++ b/integration-test/src/test/java/org/onap/cps/integration/DmiStubTestContainer.java @@ -0,0 +1,61 @@ +/* + * ============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; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +public class DmiStubTestContainer extends GenericContainer<DmiStubTestContainer> { + + public static final String IMAGE_NAME_AND_VERSION = + "nexus3.onap.org:10003/onap/dmi-plugin-demo-and-csit-stub:latest"; + public static final String DMI_STUB_URL = "http://localhost:8784"; + + private static DmiStubTestContainer dmiStubTestContainer; + + private DmiStubTestContainer() { + super(DockerImageName.parse(IMAGE_NAME_AND_VERSION)); + } + + /** + * Provides an instance of the Dmi Plugin Stub test container wrapper. + * This will allow to interact with the DMI Stub in our acceptance tests. + * + * @return DmiStubTestContainer + */ + public static DmiStubTestContainer getInstance() { + if (dmiStubTestContainer == null) { + dmiStubTestContainer = new DmiStubTestContainer(); + dmiStubTestContainer.addFixedExposedPort(8784, 8092); + Runtime.getRuntime().addShutdownHook(new Thread(dmiStubTestContainer::close)); + } + return dmiStubTestContainer; + } + + @Override + public void start() { + super.start(); + } + + @Override + public void stop() { + // Method intentionally left blank + } +} 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/java/org/onap/cps/integration/MicroMeterTestConfig.java b/integration-test/src/test/java/org/onap/cps/integration/MicroMeterTestConfig.java new file mode 100644 index 0000000000..3b26f42c8a --- /dev/null +++ b/integration-test/src/test/java/org/onap/cps/integration/MicroMeterTestConfig.java @@ -0,0 +1,41 @@ +/* + * ============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; + +import io.micrometer.core.aop.TimedAspect; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MicroMeterTestConfig { + @Bean + public MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); // Use a simple in-memory registry for testing + } + + @Bean + public TimedAspect timedAspect(final MeterRegistry meterRegistry) { + return new TimedAspect(meterRegistry); + } +} + diff --git a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java index f8a2ecb4df..46bfcf69e6 100644 --- a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java +++ b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.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. @@ -20,6 +20,8 @@ package org.onap.cps.integration; +import static org.awaitility.Awaitility.await; + import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; @@ -71,7 +73,7 @@ public class ResourceMeter { static void performGcAndWait() { final long gcCountBefore = getGcCount(); System.gc(); - while (getGcCount() == gcCountBefore) {} + await().until(() -> getGcCount() > gcCountBefore); } private static long getGcCount() { @@ -94,4 +96,3 @@ public class ResourceMeter { .forEach(MemoryPoolMXBean::resetPeakUsage); } } - 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 58e6287955..30598dfb90 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -179,9 +179,7 @@ ncmp: timers: advised-modules-sync: - sleep-time-ms: 100000 - locked-modules-sync: - sleep-time-ms: 300000 + sleep-time-ms: 1000000 cm-handle-data-sync: sleep-time-ms: 30000 subscription-forwarding: @@ -193,7 +191,7 @@ ncmp: modules-sync-watchdog: async-executor: - parallelism-level: 1 + parallelism-level: 2 model-loader: maximum-attempt-count: 20 @@ -213,8 +211,24 @@ ncmp: init: mode: ALWAYS + policy-executor: + enabled: true + defaultDecision: "test default decision" + server: + address: http://localhost + port: 8790 + httpclient: + all-services: + maximumInMemorySizeInMegabytes: 1 + maximumConnectionsTotal: 10 + pendingAcquireMaxCount: 10 + connectionTimeoutInSeconds: 30 + readTimeoutInSeconds: 1 + writeTimeoutInSeconds: 30 + hazelcast: cluster-name: cps-and-ncmp-test-caches + instance-config-name: "cps-and-ncmp-hazelcast-instance-test-config" mode: kubernetes: enabled: false 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 |