diff options
Diffstat (limited to 'integration-test/src/test')
28 files changed, 1030 insertions, 147 deletions
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 6855e49c54..759eccd966 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/base/CpsIntegrationSpecBase.groovy @@ -39,19 +39,21 @@ 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.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 @@ -60,6 +62,7 @@ import spock.util.concurrent.PollingConditions import java.time.OffsetDateTime import java.time.format.DateTimeFormatter +import java.util.concurrent.BlockingQueue import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DATASPACE_NAME import static org.onap.cps.ncmp.impl.inventory.NcmpPersistence.NCMP_DMI_REGISTRY_ANCHOR @@ -71,7 +74,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 +121,9 @@ abstract class CpsIntegrationSpecBase extends Specification { ModuleSyncWatchdog moduleSyncWatchdog @Autowired + BlockingQueue<DataNode> moduleSyncWorkQueue + + @Autowired JsonObjectMapper jsonObjectMapper @Autowired @@ -125,12 +132,24 @@ abstract class CpsIntegrationSpecBase extends Specification { @Autowired AlternateIdMatcher alternateIdMatcher - MockWebServer mockDmiServer = null - DmiDispatcher dmiDispatcher = new DmiDispatcher() - 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 @@ -144,14 +163,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() } def static readResourceDataFile(filename) { @@ -217,26 +246,43 @@ 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(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { CmHandleState.READY == networkCmProxyInventoryFacade.getCmHandleCompositeState(cmHandleId).cmHandleState }) } + def registerCmHandleWithoutWaitForReady(dmiPlugin, cmHandleId, moduleSetTag, alternateId) { + def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: cmHandleId, moduleSetTag: moduleSetTag, alternateId: alternateId) + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: [cmHandleToCreate])) + } + + def registerSequenceOfCmHandlesWithoutWaitForReady(dmiPlugin, moduleSetTag, numberOfCmHandles) { + def cmHandles = [] + (1..numberOfCmHandles).each { + def cmHandle = new NcmpServiceCmHandle(cmHandleId: 'ch-'+it, moduleSetTag: moduleSetTag, alternateId: NO_ALTERNATE_ID) + cmHandles.add(cmHandle) + } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, createdCmHandles: cmHandles)) + } + def deregisterCmHandle(dmiPlugin, cmHandleId) { deregisterCmHandles(dmiPlugin, [cmHandleId]) } def deregisterCmHandles(dmiPlugin, cmHandleIds) { - networkCmProxyInventoryFacade.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } - def overrideCmHandleLastUpdateTime(cmHandleId, newUpdateTime) { - String ISO_TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"; - DateTimeFormatter ISO_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern(ISO_TIMESTAMP_PATTERN); - def jsonForUpdate = '{ "state": { "last-update-time": "%s" } }'.formatted(ISO_TIMESTAMP_FORMATTER.format(newUpdateTime)) - cpsDataService.updateNodeLeaves(NCMP_DATASPACE_NAME, NCMP_DMI_REGISTRY_ANCHOR, - NCMP_DMI_REGISTRY_PARENT + "/cm-handles[@id='${cmHandleId}']", jsonForUpdate, now, ContentType.JSON) + def deregisterSequenceOfCmHandles(dmiPlugin, numberOfCmHandles) { + def cmHandleIds = [] + (1..numberOfCmHandles).each { cmHandleIds.add('ch-'+it) } + networkCmProxyInventoryFacade.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: dmiPlugin, removedCmHandles: cmHandleIds)) } + } diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/base/DmiDispatcher.groovy index e77815f5ec..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,9 +20,7 @@ package org.onap.cps.integration.base -import static org.onap.cps.integration.base.CpsIntegrationSpecBase.readResourceDataFile - -import java.util.regex.Matcher +import groovy.json.JsonSlurper import okhttp3.mockwebserver.Dispatcher import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.RecordedRequest @@ -30,6 +28,10 @@ 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. * @@ -53,8 +55,12 @@ class DmiDispatcher extends Dispatcher { static final MODULE_RESOURCES_RESPONSE_TEMPLATE = readResourceDataFile('mock-dmi-responses/moduleResourcesTemplate.json') 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) { @@ -79,43 +85,64 @@ class DmiDispatcher extends Dispatcher { // 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 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 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 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 mockResponse(status) { + def static mockResponse(status) { return new MockResponse().setResponseCode(status.value()) } - private static mockResponseWithBody(status, responseBody) { + def static mockResponseWithBody(status, responseBody) { return new MockResponse() .setResponseCode(status.value()) .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) 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/cps/DataServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy index a488b3b836..d49931eb7e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/DataServiceIntegrationSpec.groovy @@ -224,7 +224,7 @@ class DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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 DataServiceIntegrationSpec 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/cps/ModuleServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy index 0e465d84a0..9e51d80d9e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/ModuleServiceIntegrationSpec.groovy @@ -134,7 +134,7 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase { objectUnderTest.deleteSchemaSetsWithCascade(FUNCTIONAL_TEST_DATASPACE_1, [ 'newSchema1', 'newSchema2']) } - def 'Create schema set error scenario: #scenario.'() { + def 'Attempt to create schema set, error scenario: #scenario.'() { when: 'attempt to store schema set #schemaSetName in dataspace #dataspaceName' populateNewYangResourcesNameToContentMapAndAllModuleReferences(0) objectUnderTest.createSchemaSet(dataspaceName, schemaSetName, newYangResourcesNameToContentMap) @@ -146,6 +146,14 @@ class ModuleServiceIntegrationSpec extends FunctionalSpecBase { 'schema set already exists' | FUNCTIONAL_TEST_DATASPACE_1 | BOOKSTORE_SCHEMA_SET || AlreadyDefinedException } + def 'Attempt to create duplicate schema set from modules.'() { + when: 'attempt to store duplicate schema set from modules' + objectUnderTest.createSchemaSetFromModules(FUNCTIONAL_TEST_DATASPACE_1, BOOKSTORE_SCHEMA_SET, newYangResourcesNameToContentMap, []) + then: 'an Already Defined Exception is thrown' + thrown(AlreadyDefinedException) + } + + /* R E A D S C H E M A S E T I N F O U S E - C A S E S */ @@ -215,7 +223,8 @@ class ModuleServiceIntegrationSpec 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/cps/QueryServiceIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy index fd9aa54051..5c2a4fc665 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/QueryServiceIntegrationSpec.groovy @@ -271,7 +271,6 @@ class QueryServiceIntegrationSpec extends FunctionalSpecBase { 'ancestor with parent list' | '//books/ancestor::bookstore/categories' || ["/bookstore/categories[@code='1']", "/bookstore/categories[@code='2']", "/bookstore/categories[@code='3']", "/bookstore/categories[@code='4']", "/bookstore/categories[@code='5']"] 'ancestor with parent list element' | '//books/ancestor::bookstore/categories[@code="2"]' || ["/bookstore/categories[@code='2']"] 'ancestor combined with text condition' | '//books/title[text()="Matilda"]/ancestor::bookstore' || ["/bookstore"] - 'ancestor same as target type' | '//books/title[text()="Matilda"]/ancestor::books' || ["/bookstore/categories[@code='1']/books[@title='Matilda']"] } def 'Cps Path query across anchors with #scenario descendants.'() { @@ -383,7 +382,7 @@ class QueryServiceIntegrationSpec 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/cps/SessionManagerIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/SessionManagerIntegrationSpec.groovy index 428d5f9014..ad153d6a4a 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/SessionManagerIntegrationSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/cps/SessionManagerIntegrationSpec.groovy @@ -21,8 +21,8 @@ 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/ncmp/BearerTokenPassthroughSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy index c91e750d6e..99e80323c2 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/BearerTokenPassthroughSpec.groovy @@ -36,12 +36,12 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { def setup() { - dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] - registerCmHandle(DMI_URL, 'ch-1', NO_MODULE_SET_TAG) + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG) } def cleanup() { - deregisterCmHandle(DMI_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-1') } def 'Bearer token is passed from NCMP to DMI in pass-through data operations.'() { @@ -54,7 +54,7 @@ class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with bearer token' - assert dmiDispatcher.lastAuthHeaderReceived == 'Bearer some-bearer-token' + assert dmiDispatcher1.lastAuthHeaderReceived == 'Bearer some-bearer-token' where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] @@ -70,7 +70,7 @@ class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { .andExpect(status().is2xxSuccessful()) then: 'DMI has received request with no authorization header' - assert dmiDispatcher.lastAuthHeaderReceived == null + assert dmiDispatcher1.lastAuthHeaderReceived == null where: 'all HTTP operations are applied' httpMethod << [GET, POST, PUT, PATCH, DELETE] @@ -94,7 +94,7 @@ class BearerTokenPassthroughSpec extends CpsIntegrationSpecBase { then: 'DMI will receive the async request with bearer token' new PollingConditions().within(3, () -> { - assert dmiDispatcher.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 index c9a64e0ab8..19b10a3c79 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleCreateSpec.groovy @@ -24,6 +24,7 @@ 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.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 @@ -31,42 +32,48 @@ import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.events.lcm.v1.LcmEvent import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory +import spock.lang.Ignore import spock.util.concurrent.PollingConditions import java.time.Duration -import java.time.OffsetDateTime class CmHandleCreateSpec extends CpsIntegrationSpecBase { NetworkCmProxyInventoryFacade objectUnderTest + def uniqueId = 'ch-unique-id-for-create-test' - def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class) + def kafkaConsumer = KafkaTestContainer.getConsumer('test-group', StringDeserializer.class) def setup() { objectUnderTest = networkCmProxyInventoryFacade } + @Ignore def 'CM Handle registration is successful.'() { given: 'DMI will return modules when requested' - dmiDispatcher.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId[uniqueId] = ['M1', 'M2'] and: 'consumer subscribed to topic' kafkaConsumer.subscribe(['ncmp-events']) when: 'a CM-handle is registered for creation' - def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1') - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate]) - def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + 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('ch-1')] + assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(uniqueId)] and: 'CM-handle is initially in ADVISED state' - assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState + assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() and: 'CM-handle goes to READY state after module sync' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { - assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState + assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState }) and: 'the messages is polled' @@ -75,23 +82,33 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { and: 'the newest lcm event notification is received with READY state' def notificationMessage = jsonObjectMapper.convertJsonString(records.last().value().toString(), LcmEvent) + /*TODO (Toine) This test was failing intermittently (when running as part of suite). + I suspect that it often gave false positives as the message being assert here was any random message created by previous tests + By checking the cm-handle and using an unique cm-handle in this test this flaw became obvious. + I have now ignored this test as it is out of scope of this commit to fix it. + Created: https://lf-onap.atlassian.net/browse/CPS-2468 to fix this instead + */ + assert notificationMessage.event.cmHandleId == uniqueId assert notificationMessage.event.newValues.cmHandleState.value() == 'READY' and: 'the CM-handle has expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() + assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(uniqueId).moduleName.sort() cleanup: 'deregister CM handle' - deregisterCmHandle(DMI_URL, 'ch-1') + 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' - dmiDispatcher.isAvailable = false + dmiDispatcher1.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) + 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, () -> { @@ -104,19 +121,22 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty cleanup: 'deregister CM handle' - deregisterCmHandle(DMI_URL, 'ch-1') + deregisterCmHandle(DMI1_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']] + 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(DMI_URL, 'ch-1', 'A') - registerCmHandle(DMI_URL, 'ch-2', '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.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])) + objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])) + + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'the CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -130,36 +150,78 @@ class CmHandleCreateSpec extends CpsIntegrationSpecBase { assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort() cleanup: 'deregister CM handles' - deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2', 'ch-3']) + 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' - dmiDispatcher.isAvailable = false + dmiDispatcher1.isAvailable = false when: 'CM-handles are registered for creation' - def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1')] - def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: cmHandlesToCreate) - objectUnderTest.updateDmiRegistrationAndSyncModule(dmiPluginRegistration) + 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' - dmiDispatcher.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2']] - dmiDispatcher.isAvailable = true - and: 'the LOCKED CM handle retry time elapses (actually just subtract 3 minutes from handles lastUpdateTime)' - overrideCmHandleLastUpdateTime('ch-1', OffsetDateTime.now().minusMinutes(3)) + 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: 'CM-handle goes to READY state' + then: 'Both CM-handles go to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { - assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.READY + ['ch-1', 'ch-2'].each { cmHandleId -> + assert objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState == CmHandleState.READY + } }) - and: 'CM-handle has expected modules' - assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort() - cleanup: 'deregister CM handle' - deregisterCmHandle(DMI_URL, 'ch-1') + 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']) } } 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/ncmp/CmHandleUpgradeSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy index 35ea0793ce..64449371fe 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/CmHandleUpgradeSpec.groovy @@ -42,14 +42,14 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { 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) + dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, CM_HANDLE_ID, initialModuleSetTag) assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).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 dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)] @@ -61,7 +61,10 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { 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'] + dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M3'] + + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } then: 'CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -75,7 +78,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() cleanup: 'deregister CM-handle' - deregisterCmHandle(DMI_URL, CM_HANDLE_ID) + deregisterCmHandle(DMI1_URL, CM_HANDLE_ID) where: initialModuleSetTag | updatedModuleSetTag @@ -87,23 +90,26 @@ class CmHandleUpgradeSpec 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[CM_HANDLE_ID] = ['M1', 'M2'] + dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG] = ['M1', 'M3'] and: "an existing CM-handle handle with moduleSetTag '${updatedModuleSetTag}'" - registerCmHandle(DMI_URL, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG, updatedModuleSetTag) + registerCmHandle(DMI1_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() and: "a CM-handle with moduleSetTag '${initialModuleSetTag}' which will be upgraded" - registerCmHandle(DMI_URL, CM_HANDLE_ID, initialModuleSetTag) + registerCmHandle(DMI1_URL, CM_HANDLE_ID, initialModuleSetTag) assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).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 dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) then: 'registration gives successful response' assert dmiPluginRegistrationResponse.upgradedCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(CM_HANDLE_ID)] + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } + and: 'CM-handle goes to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(CM_HANDLE_ID).cmHandleState @@ -116,7 +122,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() cleanup: 'deregister CM-handle' - deregisterCmHandles(DMI_URL, [CM_HANDLE_ID, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG]) + deregisterCmHandles(DMI1_URL, [CM_HANDLE_ID, CM_HANDLE_ID_WITH_EXISTING_MODULE_SET_TAG]) where: initialModuleSetTag | updatedModuleSetTag @@ -126,14 +132,14 @@ class CmHandleUpgradeSpec 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') + dmiDispatcher1.moduleNamesPerCmHandleId[CM_HANDLE_ID] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, CM_HANDLE_ID, 'same') assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).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)) + 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 @@ -145,20 +151,23 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(CM_HANDLE_ID).moduleName.sort() cleanup: 'deregister CM-handle' - deregisterCmHandle(DMI_URL, CM_HANDLE_ID) + deregisterCmHandle(DMI1_URL, CM_HANDLE_ID) } 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[CM_HANDLE_ID] = ['M1', 'M2'] + registerCmHandle(DMI1_URL, CM_HANDLE_ID, '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)) + objectUnderTest.updateDmiRegistration( + new DmiPluginRegistration(dmiPlugin: DMI1_URL, upgradedCmHandles: cmHandlesToUpgrade)) + + and: 'the module sync watchdog is triggered twice' + 2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() } then: 'CM-handle goes to LOCKED state with reason MODULE_UPGRADE_FAILED' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { @@ -171,7 +180,7 @@ class CmHandleUpgradeSpec extends CpsIntegrationSpecBase { assert objectUnderTest.getNcmpServiceCmHandle(CM_HANDLE_ID).moduleSetTag == 'oldTag' cleanup: 'deregister CM-handle' - deregisterCmHandle(DMI_URL, CM_HANDLE_ID) + deregisterCmHandle(DMI1_URL, CM_HANDLE_ID) } } 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..e0bb437a7c --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/ModuleSyncWatchdogIntegrationSpec.groovy @@ -0,0 +1,99 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2024 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.integration.functional.ncmp + +import org.onap.cps.integration.base.CpsIntegrationSpecBase +import org.onap.cps.ncmp.impl.inventory.sync.ModuleSyncWatchdog + +import java.util.concurrent.Executors + +class ModuleSyncWatchdogIntegrationSpec extends CpsIntegrationSpecBase { + + ModuleSyncWatchdog objectUnderTest + + def executorService = Executors.newFixedThreadPool(2) + def SYNC_SAMPLE_SIZE = 100 + + def setup() { + objectUnderTest = moduleSyncWatchdog + registerSequenceOfCmHandlesWithoutWaitForReady(DMI1_URL, NO_MODULE_SET_TAG, SYNC_SAMPLE_SIZE) + } + + def cleanup() { + try { + deregisterSequenceOfCmHandles(DMI1_URL, SYNC_SAMPLE_SIZE) + moduleSyncWorkQueue.clear() + } finally { + executorService.shutdownNow() + } + } + + def 'Watchdog is disabled for test.'() { + when: 'wait a while but less then the initial delay of 10 minutes' + Thread.sleep(3000) + then: 'the work queue remains empty' + assert moduleSyncWorkQueue.isEmpty() + } + + def 'Populate module sync work queue simultaneously on two parallel threads (CPS-2403).'() { + // This test failed before bug https://lf-onap.atlassian.net/browse/CPS-2403 was fixed + given: 'the queue is empty at the start' + assert moduleSyncWorkQueue.isEmpty() + when: 'attempt to populate the queue on the main (test) and another parallel thread at the same time' + objectUnderTest.populateWorkQueueIfNeeded() + executorService.execute(populateQueueWithoutDelay) + and: 'wait a little (to give all threads time to complete their task)' + Thread.sleep(50) + then: 'the queue size is exactly the sample size' + assert moduleSyncWorkQueue.size() == SYNC_SAMPLE_SIZE + } + + def 'Populate module sync work queue on two parallel threads with a slight difference in start time.'() { + // This test proved that the issue in CPS-2403 did not arise if the the queue was populated and given time to be distributed + given: 'the queue is empty at the start' + assert moduleSyncWorkQueue.isEmpty() + when: 'attempt to populate the queue on the main (test) and another parallel thread a little later' + objectUnderTest.populateWorkQueueIfNeeded() + executorService.execute(populateQueueWithDelay) + and: 'wait a little (to give all threads time to complete their task)' + Thread.sleep(50) + then: 'the queue size is exactly the sample size' + assert moduleSyncWorkQueue.size() == SYNC_SAMPLE_SIZE + } + + def populateQueueWithoutDelay = () -> { + try { + objectUnderTest.populateWorkQueueIfNeeded() + } catch (InterruptedException e) { + e.printStackTrace() + } + } + + def populateQueueWithDelay = () -> { + try { + Thread.sleep(10) + objectUnderTest.populateWorkQueueIfNeeded() + } catch (InterruptedException e) { + e.printStackTrace() + } + } + +} diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy new file mode 100644 index 0000000000..56d4bfaee4 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/PolicyExecutorIntegrationSpec.groovy @@ -0,0 +1,74 @@ +/* + * ============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() { + deregisterCmHandle(DMI1_URL, 'ch-1') + deregisterCmHandle(DMI1_URL, 'ch-2') + deregisterCmHandle(DMI1_URL, 'ch-3') + } + + 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/ncmp/RestApiSpec.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy index ab189c2917..265562880e 100644 --- a/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy +++ b/integration-test/src/test/groovy/org/onap/cps/integration/functional/ncmp/RestApiSpec.groovy @@ -35,15 +35,17 @@ 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'] ] when: '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"}]}' + def requestBody = '{"dmiPlugin":"'+DMI1_URL+'","createdCmHandles":[{"cmHandle":"ch-1","alternateId":"alt-1"},{"cmHandle":"ch-2","alternateId":"alt-2"},{"cmHandle":"ch-3","alternateId":"alt-3"}]}' mvc.perform(post('/ncmpInventory/v1/ch').contentType(MediaType.APPLICATION_JSON).content(requestBody)) .andExpect(status().is2xxSuccessful()) + and: 'the module sync watchdog is triggered' + moduleSyncWatchdog.moduleSyncAdvisedCmHandles() then: 'CM-handles go to READY state' new PollingConditions().within(MODULE_SYNC_WAIT_TIME_IN_SECONDS, () -> { (1..3).each { @@ -76,9 +78,30 @@ class RestApiSpec extends CpsIntegrationSpecBase { 'M3' || ['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: + 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/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 fefae345e6..b786a3d4c5 100644 --- a/integration-test/src/test/resources/application.yml +++ b/integration-test/src/test/resources/application.yml @@ -179,9 +179,7 @@ ncmp: timers: advised-modules-sync: - sleep-time-ms: 1000 - locked-modules-sync: - sleep-time-ms: 1000 + sleep-time-ms: 1000000 cm-handle-data-sync: sleep-time-ms: 30000 subscription-forwarding: @@ -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 |