diff options
Diffstat (limited to 'cps-ncmp-service/src/test')
12 files changed, 214 insertions, 132 deletions
diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy index ca71c345c1..b988f9e171 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/config/PolicyExecutorHttpClientConfigSpec.groovy @@ -41,8 +41,13 @@ class PolicyExecutorHttpClientConfigSpec extends Specification { assert maximumConnectionsTotal == 32 assert pendingAcquireMaxCount == 33 assert connectionTimeoutInSeconds == 34 - assert readTimeoutInSeconds == 35 assert writeTimeoutInSeconds == 36 } } + + def 'Increased read timeout.'() { + expect: 'Read timeout is 10 seconds more then configured to enable a separate timeout method in policy executor with the required timeout' + assert policyExecutorHttpClientConfig.allServices.readTimeoutInSeconds == 35 + 10 + + } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy new file mode 100644 index 0000000000..e7eb893b03 --- /dev/null +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/cache/HazelcastCacheConfigSpec.groovy @@ -0,0 +1,76 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.ncmp.impl.cache + +import com.hazelcast.config.Config +import com.hazelcast.config.RestEndpointGroup +import spock.lang.Specification + +class HazelcastCacheConfigSpec extends Specification { + + def objectUnderTest = new HazelcastCacheConfig() + + def 'Create Hazelcast instance with a #scenario'() { + given: 'a cluster name' + objectUnderTest.clusterName = 'my cluster' + when: 'an hazelcast instance is created (name has to be unique)' + def result = objectUnderTest.createHazelcastInstance(scenario, config) + then: 'the instance is created and has the correct name' + assert result.name == scenario + and: 'if applicable it has a map config with the expected name' + if (expectMapConfig) { + assert result.config.mapConfigs.values()[0].name == 'my map config' + } else { + assert result.config.mapConfigs.isEmpty() + } + and: 'if applicable it has a queue config with the expected name' + if (expectQueueConfig) { + assert result.config.queueConfigs.values()[0].name == 'my queue config' + } else { + assert result.config.queueConfigs.isEmpty() + } + and: 'if applicable it has a set config with the expected name' + if (expectSetConfig) { + assert result.config.setConfigs.values()[0].name == 'my set config' + } else { + assert result.config.setConfigs.isEmpty() + } + where: 'the following configs are used' + scenario | config || expectMapConfig | expectQueueConfig | expectSetConfig + 'Map Config' | HazelcastCacheConfig.createMapConfig('my map config') || true | false | false + 'Queue Config' | HazelcastCacheConfig.createQueueConfig('my queue config') || false | true | false + 'Set Config' | HazelcastCacheConfig.createSetConfig('my set config') || false | false | true + } + + def 'Verify Hazelcast Cluster Information'() { + given: 'a test configuration' + def testConfig = new Config() + when: 'cluster information is exposed' + objectUnderTest.exposeClusterInformation(testConfig) + then: 'REST api configs are enabled' + assert testConfig.networkConfig.restApiConfig.enabled + and: 'only health check and cluster read are enabled' + def enabledGroups = testConfig.networkConfig.restApiConfig.enabledGroups + assert enabledGroups.size() == 2 + assert enabledGroups.containsAll([RestEndpointGroup.CLUSTER_READ, RestEndpointGroup.HEALTH_CHECK]) + } + +} diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy index c859bb0a09..960e6b32f3 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorConfigurationSpec.groovy @@ -39,7 +39,9 @@ class PolicyExecutorConfigurationSpec extends Specification { def 'Policy executor configuration properties.'() { expect: 'properties used from application.yml' assert objectUnderTest.enabled + assert objectUnderTest.defaultDecision == 'some default decision' assert objectUnderTest.serverAddress == 'http://localhost' assert objectUnderTest.serverPort == '8785' + assert objectUnderTest.readTimeoutInSeconds == 35 } } diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy index 63a915ab64..46c0ddeb93 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/data/policyexecutor/PolicyExecutorSpec.groovy @@ -28,7 +28,6 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.ncmp.api.exceptions.NcmpException import org.onap.cps.ncmp.api.exceptions.PolicyExecutorException -import org.onap.cps.ncmp.api.exceptions.ServerNcmpException import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus @@ -37,6 +36,8 @@ import org.springframework.web.reactive.function.client.WebClient import reactor.core.publisher.Mono import spock.lang.Specification +import java.util.concurrent.TimeoutException + import static org.onap.cps.ncmp.api.data.models.OperationType.CREATE import static org.onap.cps.ncmp.api.data.models.OperationType.DELETE import static org.onap.cps.ncmp.api.data.models.OperationType.PATCH @@ -69,52 +70,90 @@ class PolicyExecutorSpec extends Specification { ((Logger) LoggerFactory.getLogger(PolicyExecutor)).detachAndStopAllAppenders() } - def 'Permission check with allow response.'() { + def 'Permission check with "allow" decision.'() { given: 'allow response' mockResponse([decision:'allow'], HttpStatus.OK) when: 'permission is checked for an operation' objectUnderTest.checkPermission(new YangModelCmHandle(), operationType, 'my credentials','my resource',someValidJson) then: 'system logs the operation is allowed' - assert getLogEntry(2) == 'Policy Executor allows the operation' + assert getLogEntry(2) == 'Operation allowed.' and: 'no exception occurs' noExceptionThrown() where: 'all write operations are tested' operationType << [ CREATE, DELETE, PATCH, UPDATE ] } - def 'Permission check with other response (not allowed).'() { + def 'Permission check with "other" decision (not allowed).'() { given: 'other response' mockResponse([decision:'other', decisionId:123, message:'I dont like Mondays' ], HttpStatus.OK) when: 'permission is checked for an operation' objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson) then: 'Policy Executor exception is thrown' def thrownException = thrown(PolicyExecutorException) - assert thrownException.message == 'Policy Executor did not allow request. Decision #123 : other' + assert thrownException.message == 'Operation not allowed. Decision id 123 : other' assert thrownException.details == 'I dont like Mondays' } - def 'Permission check with non 2xx response.'() { - given: 'other response' + def 'Permission check with non-2xx response and "allow" default decision.'() { + given: 'other http response' mockResponse([], HttpStatus.I_AM_A_TEAPOT) + and: 'the configured default decision is "allow"' + objectUnderTest.defaultDecision = 'allow' when: 'permission is checked for an operation' objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials','my resource',someValidJson) - then: 'Server Ncmp exception is thrown' - def thrownException = thrown(ServerNcmpException) - assert thrownException.message == 'Policy Executor invocation failed' - assert thrownException.details == 'HTTP status code: 418' + then: 'No exception is thrown' + noExceptionThrown() + } + + def 'Permission check with non-2xx response and "other" default decision.'() { + given: 'other http response' + mockResponse([], HttpStatus.I_AM_A_TEAPOT) + and: 'the configured default decision is NOT "allow"' + objectUnderTest.defaultDecision = 'deny by default' + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), PATCH, 'my credentials', 'my resource', someValidJson) + then: 'Policy Executor exception is thrown' + def thrownException = thrown(PolicyExecutorException) + assert thrownException.message == 'Operation not allowed. Decision id N/A : deny by default' + assert thrownException.details == 'Policy Executor returned HTTP Status code 418. Falling back to configured default decision: deny by default' } def 'Permission check with invalid response from Policy Executor.'() { given: 'invalid response from Policy executor' mockResponseSpec.toEntity(*_) >> invalidResponse when: 'permission is checked for an operation' - objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials','my resource',someValidJson) + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', someValidJson) then: 'system logs the expected message' assert getLogEntry(1) == expectedMessage where: 'following invalid responses are received' - invalidResponse || expectedMessage - Mono.empty() || 'No valid response from policy, ignored' - Mono.just(new ResponseEntity<>(null, HttpStatus.OK)) || 'No valid response body from policy, ignored' + invalidResponse || expectedMessage + Mono.empty() || 'No valid response from Policy Executor, ignored' + Mono.just(new ResponseEntity<>(null, HttpStatus.OK)) || 'No valid response body from Policy Executor, ignored' + } + + def 'Permission check with timeout exception.'() { + given: 'a timeout during the request' + def cause = new TimeoutException() + mockResponseSpec.toEntity(*_) >> { throw new RuntimeException(cause) } + and: 'the configured default decision is NOT "allow"' + objectUnderTest.defaultDecision = 'deny by default' + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', someValidJson) + then: 'Policy Executor exception is thrown' + def thrownException = thrown(PolicyExecutorException) + assert thrownException.message == 'Operation not allowed. Decision id N/A : deny by default' + assert thrownException.details == 'Policy Executor request timed out. Falling back to configured default decision: deny by default' + } + + def 'Permission check with other runtime exception.'() { + given: 'some other runtime exception' + def originalException = new RuntimeException() + mockResponseSpec.toEntity(*_) >> { throw originalException} + when: 'permission is checked for an operation' + objectUnderTest.checkPermission(new YangModelCmHandle(), CREATE, 'my credentials', 'my resource', someValidJson) + then: 'The original exception is thrown' + def thrownException = thrown(RuntimeException) + assert thrownException == originalException } def 'Permission check with an invalid change request json.'() { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy index cb3c4ffec1..7e34fe2822 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/CmHandleQueryServiceImplSpec.groovy @@ -23,10 +23,10 @@ package org.onap.cps.ncmp.impl.inventory import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsQueryService +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.spi.model.DataNode -import org.onap.cps.spi.utils.CpsValidator import spock.lang.Shared import spock.lang.Specification diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy index fdf12a880d..1830f1331d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/InventoryPersistenceImplSpec.groovy @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper import org.onap.cps.api.CpsAnchorService import org.onap.cps.api.CpsDataService import org.onap.cps.api.CpsModuleService +import org.onap.cps.impl.utils.CpsValidator import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.impl.inventory.models.CmHandleState import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle @@ -35,7 +36,6 @@ import org.onap.cps.spi.exceptions.DataNodeNotFoundException import org.onap.cps.spi.model.DataNode import org.onap.cps.spi.model.ModuleDefinition import org.onap.cps.spi.model.ModuleReference -import org.onap.cps.spi.utils.CpsValidator import org.onap.cps.utils.ContentType import org.onap.cps.utils.JsonObjectMapper import spock.lang.Shared diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy index 9e07de48bf..fec07556eb 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/NetworkCmProxyInventoryFacadeSpec.groovy @@ -244,8 +244,7 @@ class NetworkCmProxyInventoryFacadeSpec extends Specification { and: 'query cm handle method returns two cm handles' mockParameterizedCmHandleQueryService.queryCmHandles( spiedJsonObjectMapper.convertToValueType(cmHandleQueryApiParameters, CmHandleQueryServiceParameters.class)) - >> [new YangModelCmHandle(id: 'ch-0', dmiProperties: [], publicProperties: []), - new YangModelCmHandle(id: 'ch-1', dmiProperties: [], publicProperties: [])] + >> [new NcmpServiceCmHandle(cmHandleId: 'ch-0'), new NcmpServiceCmHandle(cmHandleId: 'ch-1')] and: 'a trust level for cm handles' mockTrustLevelManager.getEffectiveTrustLevel(*_) >> TrustLevel.COMPLETE when: 'execute cm handle search is called' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy index 08644202c6..013bace04d 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/ParameterizedCmHandleQueryServiceSpec.groovy @@ -1,6 +1,6 @@ /* * ============LICENSE_START======================================================= - * Copyright (C) 2022-2024 Nordix Foundation + * Copyright (C) 2022-2023 Nordix Foundation * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ package org.onap.cps.ncmp.impl.inventory import org.onap.cps.cpspath.parser.PathParsingException import org.onap.cps.ncmp.api.inventory.models.CmHandleQueryServiceParameters +import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.spi.FetchDescendantsOption import org.onap.cps.spi.exceptions.DataInUseException @@ -138,10 +139,10 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { and: 'the inventory service is called with teh correct if and returns a yang model cm handle' 1 * mockInventoryPersistence.getYangModelCmHandles(['ch1']) >> [new YangModelCmHandle(id: 'abc', dmiProperties: [new YangModelCmHandle.Property('name','value')], publicProperties: [])] - and: 'the expected cm handle(s) are returned as Yang Model cm handles' - assert result[0] instanceof YangModelCmHandle + and: 'the expected cm handle(s) are returned as NCMP Service cm handles' + assert result[0] instanceof NcmpServiceCmHandle assert result.size() == 1 - assert result[0].dmiProperties.size() == 1 + assert result[0].dmiProperties == [name:'value'] } def 'Query cm handle ids when the query is empty.'() { @@ -164,7 +165,7 @@ class ParameterizedCmHandleQueryServiceSpec extends Specification { def result = objectUnderTest.queryCmHandles(cmHandleQueryParameters) then: 'the correct cm handles are returned' assert result.size() == 4 - assert result.id.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') + assert result.cmHandleId.containsAll('PNFDemo1', 'PNFDemo2', 'PNFDemo3', 'PNFDemo4') } def 'Query CMHandleId with #scenario.' () { diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy index babe8101dd..65b7ff6d6f 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleOperationsUtilsSpec.groovy @@ -40,12 +40,8 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity import spock.lang.Specification - -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter import java.util.stream.Collectors -import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.LOCKED_MISBEHAVING import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED @@ -60,17 +56,12 @@ class ModuleOperationsUtilsSpec extends Specification{ def objectUnderTest = new ModuleOperationsUtils(mockCmHandleQueries, mockDmiDataOperations, jsonObjectMapper) - def static neverUpdatedBefore = '1900-01-01T00:00:00.000+0100' - - def static now = OffsetDateTime.now() - - def static nowAsString = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now) - def static dataNode = new DataNode(leaves: ['id': 'cm-handle-123']) def applicationContext = new AnnotationConfigApplicationContext() def logger = (Logger) LoggerFactory.getLogger(ModuleOperationsUtils) + def loggingListAppender void setup() { @@ -103,7 +94,7 @@ class ModuleOperationsUtilsSpec extends Specification{ given: 'A locked state' def compositeState = new CompositeState(lockReason: lockReason) when: 'update cm handle details and attempts is called' - objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message') + objectUnderTest.updateLockReasonWithAttempts(compositeState, MODULE_SYNC_FAILED, 'new error message') then: 'the composite state lock reason and details are updated' assert compositeState.lockReason.lockReasonCategory == MODULE_SYNC_FAILED assert compositeState.lockReason.details.contains(expectedDetails) @@ -118,14 +109,14 @@ class ModuleOperationsUtilsSpec extends Specification{ def compositeState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) .withLockReason(MODULE_UPGRADE, "Upgrade to ModuleSetTag: " + moduleSetTag).build() when: 'update cm handle details' - objectUnderTest.updateLockReasonDetailsAndAttempts(compositeState, MODULE_UPGRADE_FAILED, 'new error message') + objectUnderTest.updateLockReasonWithAttempts(compositeState, MODULE_UPGRADE_FAILED, 'new error message') then: 'the composite state lock reason and details are updated' assert compositeState.lockReason.lockReasonCategory == MODULE_UPGRADE_FAILED - assert compositeState.lockReason.details.contains("Upgrade to ModuleSetTag: " + expectedDetails) + assert compositeState.lockReason.details.contains(expectedDetails) where: scenario | moduleSetTag || expectedDetails - 'a module set tag' | 'someModuleSetTag' || 'someModuleSetTag' - 'empty module set tag' | '' || '' + 'a module set tag' | 'someModuleSetTag' || 'Upgrade to ModuleSetTag: someModuleSetTag' + 'empty module set tag' | '' || 'Attempt' } def 'Get all locked cm-Handles where lock reasons are model sync failed or upgrade'() { @@ -135,49 +126,9 @@ class ModuleOperationsUtilsSpec extends Specification{ when: 'get locked Misbehaving cm handle is called' def result = objectUnderTest.getCmHandlesThatFailedModelSyncOrUpgrade() then: 'the returned cm handle collection is the correct size' - result.size() == 1 + assert result.size() == 1 and: 'the correct cm handle is returned' - result[0].id == 'cm-handle-123' - } - - def 'Retry Locked Cm-Handle where the last update time is #scenario'() { - given: 'Last update was #lastUpdateMinutesAgo minutes ago (-1 means never)' - def lastUpdatedTime = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(now.minusMinutes(lastUpdateMinutesAgo)) - if (lastUpdateMinutesAgo < 0 ) { - lastUpdatedTime = neverUpdatedBefore - } - when: 'checking to see if cm handle is ready for retry' - def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() - .withLockReason(MODULE_SYNC_FAILED, lockDetails) - .withLastUpdatedTime(lastUpdatedTime).build()) - then: 'retry is only attempted when expected' - assert result == retryExpected - and: 'logs contain related information' - def logs = loggingListAppender.list.toString() - assert logs.contains(logReason) - where: 'the following parameters are used' - scenario | lastUpdateMinutesAgo | lockDetails | logReason || retryExpected - 'never attempted before' | -1 | 'Fist attempt:' | 'First Attempt:' || true - '1st attempt, last attempt > 2 minute ago' | 3 | 'Attempt #1 failed: some error' | 'Retry due now' || true - '2nd attempt, last attempt < 4 minutes ago' | 1 | 'Attempt #2 failed: some error' | 'Time until next attempt is 3 minutes:' || false - '2nd attempt, last attempt > 4 minutes ago' | 5 | 'Attempt #2 failed: some error' | 'Retry due now' || true - } - - def 'Retry Locked Cm-Handle with lock reasons (category) #lockReasonCategory'() { - when: 'checking to see if cm handle is ready for retry' - def result = objectUnderTest.needsModuleSyncRetryOrUpgrade(new CompositeStateBuilder() - .withLockReason(lockReasonCategory, 'some details') - .withLastUpdatedTime(nowAsString).build()) - then: 'verify retry attempts' - assert !result - and: 'logs contain related information' - def logs = loggingListAppender.list.toString() - assert logs.contains(logReason) - where: 'the following lock reasons occurred' - scenario | lockReasonCategory || logReason - 'module upgrade' | MODULE_UPGRADE_FAILED || 'First Attempt:' - 'module sync failed' | MODULE_SYNC_FAILED || 'First Attempt:' - 'lock misbehaving' | LOCKED_MISBEHAVING || 'Locked for other reason' + assert result[0].id == 'cm-handle-123' } def 'Get a Cm-Handle where #scenario'() { @@ -197,19 +148,25 @@ class ModuleOperationsUtilsSpec extends Specification{ 'all Cm-Handle synchronized' | [] | false || 0 | [] as Set } - def 'Get resource data through DMI Operations #scenario'() { - given: 'the inventory persistence service returns a collection of data nodes' + def 'Retrieve resource data from DMI operations for #scenario'() { + given: 'a JSON string representing the resource data' def jsonString = '{"stores:bookstore":{"categories":[{"code":"01"}]}}' - JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString); - def responseEntity = new ResponseEntity<>(jsonNode, HttpStatus.OK) + JsonNode jsonNode = jsonObjectMapper.convertToJsonNode(jsonString) + and: 'DMI operations are mocked to return a response based on the scenario' + def responseEntity = new ResponseEntity<>(statusCode == HttpStatus.OK ? jsonNode : null, statusCode) mockDmiDataOperations.getAllResourceDataFromDmi('cm-handle-123', _) >> responseEntity when: 'get resource data is called' def result = objectUnderTest.getResourceData('cm-handle-123') - then: 'the returned data is correct' - result == jsonString + then: 'the returned data matches the expected result' + assert result == expectedResult + where: + scenario | statusCode | expectedResult + 'successful response' | HttpStatus.OK | '{"stores:bookstore":{"categories":[{"code":"01"}]}}' + 'response with not found status' | HttpStatus.NOT_FOUND | null + 'response with internal server error' | HttpStatus.INTERNAL_SERVER_ERROR | null } - def 'Extract module set tag and number of attempt when lock reason contains #scenario'() { + def 'Extract module set tag and number of attempt when lock reason contains #scenario'() { expect: 'lock reason details are extracted correctly' def result = objectUnderTest.getLockedCompositeStateDetails(new CompositeStateBuilder().withLockReason(MODULE_UPGRADE, lockReasonDetails).build().lockReason) and: 'the result contains the correct moduleSetTag' diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy index ee49f2f901..160744a7d7 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/sync/ModuleSyncTasksSpec.groovy @@ -32,16 +32,15 @@ import org.onap.cps.ncmp.api.inventory.models.CompositeState import org.onap.cps.ncmp.api.inventory.models.CompositeStateBuilder import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.CmHandleState -import org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.impl.inventory.sync.lcm.LcmEventsCmHandleStateHandler import org.onap.cps.spi.model.DataNode import org.slf4j.LoggerFactory import spock.lang.Specification - import java.util.concurrent.atomic.AtomicInteger import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_SYNC_FAILED +import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE import static org.onap.cps.ncmp.impl.inventory.models.LockReasonCategory.MODULE_UPGRADE_FAILED class ModuleSyncTasksSpec extends Specification { @@ -96,70 +95,52 @@ class ModuleSyncTasksSpec extends Specification { assert batchCount.get() == 4 } - def 'Module Sync ADVISED cm handle with failure during sync.'() { - given: 'a cm handle in an ADVISED state' - def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.ADVISED) - and: 'the inventory persistence cm handle returns a ADVISED state for the cm handle' - def cmHandleState = new CompositeState(cmHandleState: CmHandleState.ADVISED) - 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> cmHandleState - and: 'module sync service attempts to sync the cm handle and throws an exception' - 1 * mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } + def 'Handle CM handle failure during #scenario and log MODULE_UPGRADE lock reason'() { + given: 'a CM handle in LOCKED state with a specific lock reason' + def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED) + def expectedCmHandleState = new CompositeState(cmHandleState: CmHandleState.LOCKED, lockReason: CompositeState + .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build()) + 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState + and: 'module sync service attempts to sync/upgrade the CM handle and throws an exception' + mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(_) >> { throw new Exception('some exception') } + mockModuleSyncService.syncAndUpgradeSchemaSet(_) >> { throw new Exception('some exception') } when: 'module sync is executed' objectUnderTest.performModuleSync([cmHandle], batchCount) - then: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(cmHandleState, MODULE_SYNC_FAILED, 'some exception') + then: 'lock reason is updated with number of attempts' + 1 * mockSyncUtils.updateLockReasonWithAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception') and: 'the state handler is called to update the state to LOCKED' 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(_) >> { args -> assertBatch(args, ['cm-handle'], CmHandleState.LOCKED) } and: 'batch count is decremented by one' assert batchCount.get() == 4 - } - - def 'Failed cm handle during #scenario.'() { - given: 'a cm handle in LOCKED state' - def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED) - and: 'the inventory persistence cm handle returns a LOCKED state with reason for the cm handle' - def expectedCmHandleState = new CompositeState(cmHandleState: cmHandleState, lockReason: CompositeState - .LockReason.builder().lockReasonCategory(lockReasonCategory).details(lockReasonDetails).build()) - 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> expectedCmHandleState - and: 'module sync service attempts to sync/upgrade the cm handle and throws an exception' - mockModuleSyncService.syncAndCreateSchemaSetAndAnchor(*_) >> { throw new Exception('some exception') } - mockModuleSyncService.syncAndUpgradeSchemaSet(*_) >> { throw new Exception('some exception') } - when: 'module sync is executed' - objectUnderTest.performModuleSync([cmHandle], batchCount) - then: 'update lock reason, details and attempts is invoked' - 1 * mockSyncUtils.updateLockReasonDetailsAndAttempts(expectedCmHandleState, expectedLockReasonCategory, 'some exception') where: - scenario | cmHandleState | lockReasonCategory | lockReasonDetails || expectedLockReasonCategory - 'module upgrade' | CmHandleState.LOCKED | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED - 'module sync' | CmHandleState.LOCKED | MODULE_SYNC_FAILED | 'some lock details' || MODULE_SYNC_FAILED + scenario | lockReasonCategory | lockReasonDetails || expectedLockReasonCategory + 'module sync' | MODULE_SYNC_FAILED | 'some lock details' || MODULE_SYNC_FAILED + 'module upgrade' | MODULE_UPGRADE_FAILED | 'Upgrade to ModuleSetTag: some-module-set-tag' || MODULE_UPGRADE_FAILED + 'module upgrade' | MODULE_UPGRADE | 'Upgrade in progress' || MODULE_UPGRADE_FAILED } + def 'Reset failed CM Handles #scenario.'() { given: 'cm handles in an locked state' def lockedState = new CompositeStateBuilder().withCmHandleState(CmHandleState.LOCKED) - .withLockReason(LockReasonCategory.MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build() + .withLockReason(MODULE_SYNC_FAILED, '').withLastUpdatedTimeNow().build() def yangModelCmHandle1 = new YangModelCmHandle(id: 'cm-handle-1', compositeState: lockedState) def yangModelCmHandle2 = new YangModelCmHandle(id: 'cm-handle-2', compositeState: lockedState) - def expectedCmHandleStatePerCmHandle = [(yangModelCmHandle1): CmHandleState.ADVISED] + def expectedCmHandleStatePerCmHandle + = [(yangModelCmHandle1): CmHandleState.ADVISED, (yangModelCmHandle2): CmHandleState.ADVISED] and: 'clear in progress map' resetModuleSyncStartedOnCmHandles(moduleSyncStartedOnCmHandles) and: 'add cm handle entry into progress map' moduleSyncStartedOnCmHandles.put('cm-handle-1', 'started') moduleSyncStartedOnCmHandles.put('cm-handle-2', 'started') - and: 'sync utils retry locked cm handle returns #isReadyForRetry' - mockSyncUtils.needsModuleSyncRetryOrUpgrade(lockedState) >>> isReadyForRetry when: 'resetting failed cm handles' objectUnderTest.resetFailedCmHandles([yangModelCmHandle1, yangModelCmHandle2]) then: 'updated to state "ADVISED" from "READY" is called as often as there are cm handles ready for retry' - expectedNumberOfInvocationsToUpdateCmHandleState * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle) - and: 'after reset performed size of in progress map' - assert moduleSyncStartedOnCmHandles.size() == inProgressMapSize - where: - scenario | isReadyForRetry | inProgressMapSize || expectedNumberOfInvocationsToUpdateCmHandleState - 'retry locked cm handle' | [true, false] | 1 || 1 - 'do not retry locked cm handle' | [false, false] | 2 || 0 + 1 * mockLcmEventsCmHandleStateHandler.updateCmHandleStateBatch(expectedCmHandleStatePerCmHandle) + and: 'after reset performed progress map is empty' + assert moduleSyncStartedOnCmHandles.size() == 0 } def 'Module Sync ADVISED cm handle without entry in progress map.'() { @@ -189,6 +170,24 @@ class ModuleSyncTasksSpec extends Specification { assert loggingEvent.formattedMessage == 'ch-1 removed from in progress map' } + def 'Sync and upgrade CM handle if in upgrade state for #scenario'() { + given: 'a CM handle in an upgrade state' + def cmHandle = cmHandleAsDataNodeByIdAndState('cm-handle', CmHandleState.LOCKED) + def compositeState = new CompositeState(lockReason: CompositeState.LockReason.builder().lockReasonCategory(lockReasonCategory).build()) + 1 * mockInventoryPersistence.getCmHandleState('cm-handle') >> compositeState + when: 'module sync is executed' + objectUnderTest.performModuleSync([cmHandle], batchCount) + then: 'the module sync service should attempt to sync and upgrade the CM handle' + 1 * mockModuleSyncService.syncAndUpgradeSchemaSet(_) >> { args -> + assert args[0].id == 'cm-handle' + } + where: 'the following lock reasons are used' + scenario | lockReasonCategory + 'module upgrade' | MODULE_UPGRADE + 'module upgrade failed' | MODULE_UPGRADE_FAILED + } + + def 'Remove non-existing cm handle id from hazelcast map'() { given: 'hazelcast map does not contains cm handle id' def result = moduleSyncStartedOnCmHandles.get('non-existing-cm-handle') diff --git a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy index 7dc9602e46..fe762f891a 100644 --- a/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy +++ b/cps-ncmp-service/src/test/groovy/org/onap/cps/ncmp/impl/inventory/trustlevel/TrustLevelManagerSpec.groovy @@ -25,6 +25,7 @@ import org.onap.cps.ncmp.api.inventory.models.TrustLevel import org.onap.cps.ncmp.impl.inventory.InventoryPersistence import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle import org.onap.cps.ncmp.utils.events.CmAvcEventPublisher +import spock.lang.Ignore import spock.lang.Specification class TrustLevelManagerSpec extends Specification { @@ -135,13 +136,15 @@ class TrustLevelManagerSpec extends Specification { 0 * mockAttributeValueChangeEventPublisher.publishAvcEvent(*_) } + @Ignore + // TODO: CPS-2375 def 'Select effective trust level among CmHandle and dmi plugin'() { given: 'a non trusted dmi' trustLevelPerDmiPlugin.put('my-dmi', TrustLevel.NONE) and: 'a trusted CmHandle' trustLevelPerCmHandle.put('ch-1', TrustLevel.COMPLETE) when: 'effective trust level selected' - def effectiveTrustLevel = objectUnderTest.getEffectiveTrustLevel('my-dmi', 'ch-1') + def effectiveTrustLevel = objectUnderTest.getEffectiveTrustLevel('ch-1') then: 'effective trust level is trusted' assert effectiveTrustLevel == TrustLevel.NONE } diff --git a/cps-ncmp-service/src/test/resources/application.yml b/cps-ncmp-service/src/test/resources/application.yml index c76831da74..df3375d5d0 100644 --- a/cps-ncmp-service/src/test/resources/application.yml +++ b/cps-ncmp-service/src/test/resources/application.yml @@ -83,6 +83,7 @@ ncmp: policy-executor: enabled: true + defaultDecision: "some default decision" server: address: http://localhost port: 8785 |