2 * ============LICENSE_START=======================================================
3 * Copyright (C) 2024 Nordix Foundation
4 * ================================================================================
5 * Licensed under the Apache License, Version 2.0 (the 'License');
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an 'AS IS' BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 * SPDX-License-Identifier: Apache-2.0
18 * ============LICENSE_END=========================================================
21 package org.onap.cps.integration.functional
23 import java.time.Duration
24 import java.time.OffsetDateTime
25 import org.apache.kafka.common.TopicPartition
26 import org.apache.kafka.common.serialization.StringDeserializer
27 import org.onap.cps.integration.KafkaTestContainer
28 import org.onap.cps.integration.base.CpsIntegrationSpecBase
29 import org.onap.cps.ncmp.api.NetworkCmProxyDataService
30 import org.onap.cps.ncmp.api.impl.inventory.CmHandleState
31 import org.onap.cps.ncmp.api.impl.inventory.LockReasonCategory
32 import org.onap.cps.ncmp.api.models.CmHandleRegistrationResponse
33 import org.onap.cps.ncmp.api.models.DmiPluginRegistration
34 import org.onap.cps.ncmp.api.models.NcmpServiceCmHandle
35 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent
36 import spock.util.concurrent.PollingConditions
38 class NcmpCmHandleCreateSpec extends CpsIntegrationSpecBase {
40 NetworkCmProxyDataService objectUnderTest
42 def kafkaConsumer = KafkaTestContainer.getConsumer('ncmp-group', StringDeserializer.class)
44 static final MODULE_REFERENCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_Response.json')
45 static final MODULE_RESOURCES_RESPONSE_A = readResourceDataFile('mock-dmi-responses/bookStoreAWithModules_M1_M2_ResourcesResponse.json')
46 static final MODULE_REFERENCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_Response.json')
47 static final MODULE_RESOURCES_RESPONSE_B = readResourceDataFile('mock-dmi-responses/bookStoreBWithModules_M1_M3_ResourcesResponse.json')
50 objectUnderTest = networkCmProxyDataService
51 mockDmiWillRespondToHealthChecks(DMI_URL)
54 def 'CM Handle registration is successful.'() {
55 given: 'DMI will return modules when requested'
56 mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A)
58 and: 'consumer subscribed to topic'
59 kafkaConsumer.subscribe(['ncmp-events'])
61 when: 'a CM-handle is registered for creation'
62 def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
63 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
64 def dmiPluginRegistrationResponse = networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
66 then: 'registration gives successful response'
67 assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse('ch-1')]
69 and: 'CM-handle is initially in ADVISED state'
70 assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
72 when: 'module sync runs'
73 moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
75 then: 'CM-handle goes to READY state'
76 new PollingConditions().within(3, () -> {
77 assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState
80 and: 'the messages is polled'
81 def message = kafkaConsumer.poll(Duration.ofMillis(10000))
82 def records = message.records(new TopicPartition('ncmp-events', 0))
84 and: 'the newest lcm event notification is received with READY state'
85 def notificationMessage = jsonObjectMapper.convertJsonString(records.last().value().toString(), LcmEvent)
86 assert notificationMessage.event.newValues.cmHandleState.value() == 'READY'
88 and: 'the CM-handle has expected modules'
89 assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
91 and: 'DMI received expected requests'
92 mockDmiServer.verify()
94 cleanup: 'deregister CM handle'
95 deregisterCmHandle(DMI_URL, 'ch-1')
98 def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() {
99 given: 'DMI is not available to handle requests'
100 mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-1')
102 when: 'a CM-handle is registered for creation'
103 def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
104 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate])
105 networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
107 and: 'module sync runs'
108 moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
110 then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED'
111 new PollingConditions().within(3, () -> {
112 def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState('ch-1')
113 assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
114 assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_SYNC_FAILED
117 and: 'CM-handle has no modules'
118 assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty
120 cleanup: 'deregister CM handle'
121 deregisterCmHandle(DMI_URL, 'ch-1')
124 def 'Create a CM-handle with existing moduleSetTag.'() {
125 given: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"'
126 mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A)
127 mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B)
128 registerCmHandle(DMI_URL, 'ch-1', 'A')
129 registerCmHandle(DMI_URL, 'ch-2', 'B')
130 assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
131 assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort()
133 when: 'a CM-handle is registered for creation with moduleSetTag "B"'
134 def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B')
135 networkCmProxyDataService.updateDmiRegistrationAndSyncModule(new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: [cmHandleToCreate]))
137 then: 'the CM-handle goes to READY state after module sync'
138 moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
139 new PollingConditions().within(3, () -> {
140 assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-3').cmHandleState
143 and: 'the CM-handle has expected moduleSetTag'
144 assert objectUnderTest.getNcmpServiceCmHandle('ch-3').moduleSetTag == 'B'
146 and: 'the CM-handle has expected modules from module set "B": M1 and M3'
147 assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort()
149 cleanup: 'deregister CM handles'
150 deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2', 'ch-3'])
153 def 'CM Handle retry after failed module sync.'() {
154 given: 'DMI is not initially available to handle requests'
155 mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-1')
156 mockDmiIsNotAvailableForModuleSync(DMI_URL, 'ch-2')
157 and: 'DMI will be available for retry'
158 mockDmiResponsesForModuleSync(DMI_URL, 'ch-1', MODULE_REFERENCES_RESPONSE_A, MODULE_RESOURCES_RESPONSE_A)
159 mockDmiResponsesForModuleSync(DMI_URL, 'ch-2', MODULE_REFERENCES_RESPONSE_B, MODULE_RESOURCES_RESPONSE_B)
161 when: 'CM-handles are registered for creation'
162 def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')]
163 def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI_URL, createdCmHandles: cmHandlesToCreate)
164 networkCmProxyDataService.updateDmiRegistrationAndSyncModule(dmiPluginRegistration)
165 and: 'module sync runs'
166 moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
167 then: 'CM-handles go to LOCKED state'
168 new PollingConditions().within(3, () -> {
169 assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.LOCKED
170 assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.LOCKED
173 when: 'we wait for LOCKED CM handle retry time (actually just subtract 3 minutes from handles lastUpdateTime)'
174 overrideCmHandleLastUpdateTime('ch-1', OffsetDateTime.now().minusMinutes(3))
175 overrideCmHandleLastUpdateTime('ch-2', OffsetDateTime.now().minusMinutes(3))
176 and: 'failed CM handles are reset'
177 moduleSyncWatchdog.resetPreviouslyFailedCmHandles()
178 then: 'CM-handles are ADVISED state'
179 assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.ADVISED
180 assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.ADVISED
182 when: 'module sync runs'
183 moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
184 then: 'CM-handles go to READY state'
185 new PollingConditions().within(3, () -> {
186 assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.READY
187 assert objectUnderTest.getCmHandleCompositeState('ch-2').cmHandleState == CmHandleState.READY
189 and: 'CM-handles have expected modules'
190 assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences('ch-1').moduleName.sort()
191 assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-2').moduleName.sort()
192 and: 'CM-handles have expected module set tags (blank)'
193 assert objectUnderTest.getNcmpServiceCmHandle('ch-1').moduleSetTag == ''
194 assert objectUnderTest.getNcmpServiceCmHandle('ch-2').moduleSetTag == ''
195 and: 'DMI received expected requests'
196 mockDmiServer.verify()
198 cleanup: 'deregister CM handle'
199 deregisterCmHandles(DMI_URL, ['ch-1', 'ch-2'])