ca8ebb0800b94e2b11ce3282ee85d7a0b5809e16
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved.
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
8  *
9  *        http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  *
17  *  SPDX-License-Identifier: Apache-2.0
18  *  ============LICENSE_END=========================================================
19  */
20
21 package org.onap.cps.integration.functional.ncmp.inventory
22
23 import org.apache.kafka.clients.consumer.KafkaConsumer
24 import org.onap.cps.events.LegacyEvent
25 import org.onap.cps.integration.KafkaTestContainer
26 import org.onap.cps.integration.base.CpsIntegrationSpecBase
27 import org.onap.cps.ncmp.api.NcmpResponseStatus
28 import org.onap.cps.ncmp.api.inventory.models.CmHandleRegistrationResponse
29 import org.onap.cps.ncmp.api.inventory.models.CmHandleState
30 import org.onap.cps.ncmp.api.inventory.models.DmiPluginRegistration
31 import org.onap.cps.ncmp.api.inventory.models.LockReasonCategory
32 import org.onap.cps.ncmp.api.inventory.models.NcmpServiceCmHandle
33 import org.onap.cps.ncmp.events.lcm.v1.LcmEvent
34 import org.onap.cps.ncmp.impl.NetworkCmProxyInventoryFacadeImpl
35
36 import java.time.Duration
37
38 class CmHandleCreateSpec extends CpsIntegrationSpecBase {
39
40     NetworkCmProxyInventoryFacadeImpl objectUnderTest
41     def uniqueId = 'ch-unique-id-for-create-test'
42
43     KafkaConsumer<String, LegacyEvent> kafkaConsumer
44
45     def setup() {
46         objectUnderTest = networkCmProxyInventoryFacade
47         subscribeAndClearPreviousMessages('test-group', 'ncmp-events')
48     }
49
50     def cleanup() {
51         kafkaConsumer.unsubscribe()
52         kafkaConsumer.close()
53     }
54
55     def 'CM Handle registration.'() {
56         given: 'DMI will return modules when requested'
57             dmiDispatcher1.moduleNamesPerCmHandleId['ch-1'] = ['M1', 'M2']
58             dmiDispatcher1.moduleNamesPerCmHandleId[uniqueId] = ['M1', 'M2']
59
60         when: 'a CM-handle is registered for creation'
61             def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: uniqueId, dataProducerIdentifier: 'my-data-producer-identifier')
62             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])
63             def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
64
65         then: 'registration gives successful response'
66             assert dmiPluginRegistrationResponse.createdCmHandles == [CmHandleRegistrationResponse.createSuccessResponse(uniqueId)]
67
68         and: 'CM-handle is initially in ADVISED state'
69             assert CmHandleState.ADVISED == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState
70
71         then: 'the module sync watchdog is triggered'
72             moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
73
74         then: 'CM-handle goes to READY state after module sync'
75             assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState(uniqueId).cmHandleState
76
77         and: 'the CM-handle has expected modules'
78             assert ['M1', 'M2'] == objectUnderTest.getYangResourcesModuleReferences(uniqueId).moduleName.sort()
79
80         then: 'get the latest messages'
81             def consumerRecords = getLatestConsumerRecordsWithMaxPollOf1Second(kafkaConsumer, 2)
82
83         and: 'both converted messages are for the correct cm handle'
84             def notificationMessages = []
85             for (def consumerRecord : consumerRecords) {
86                 notificationMessages.add(jsonObjectMapper.convertJsonString(consumerRecord.value().toString(), LcmEvent))
87             }
88             assert notificationMessages.event.cmHandleId == [ uniqueId, uniqueId ]
89
90         and: 'the oldest event is about the update to ADVISED state and it has a data producer id'
91             assert notificationMessages[0].event.newValues.cmHandleState.value() == 'ADVISED'
92             assert notificationMessages[0].event.dataProducerIdentifier == 'my-data-producer-identifier'
93
94         and: 'the next event is about update to READY state and it has the same data producer identifier as in in ADVISED state'
95             assert notificationMessages[1].event.newValues.cmHandleState.value() == 'READY'
96             assert notificationMessages[1].event.dataProducerIdentifier == 'my-data-producer-identifier'
97
98         and: 'there are no more messages to be read'
99             assert getLatestConsumerRecordsWithMaxPollOf1Second(kafkaConsumer, 1).size() == 0
100
101         cleanup: 'deregister CM handle'
102             deregisterCmHandle(DMI1_URL, uniqueId)
103     }
104
105     def 'CM Handle goes to LOCKED state when DMI gives error during module sync.'() {
106         given: 'DMI is not available to handle requests'
107             dmiDispatcher1.isAvailable = false
108
109         when: 'a CM-handle is registered for creation'
110             def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-1')
111             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate])
112             objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
113
114         and: 'the module sync watchdog is triggered'
115             moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
116
117         then: 'CM-handle goes to LOCKED state with reason MODULE_SYNC_FAILED'
118             def cmHandleCompositeState = objectUnderTest.getCmHandleCompositeState('ch-1')
119             assert cmHandleCompositeState.cmHandleState == CmHandleState.LOCKED
120             assert cmHandleCompositeState.lockReason.lockReasonCategory == LockReasonCategory.MODULE_SYNC_FAILED
121
122         and: 'CM-handle has no modules'
123             assert objectUnderTest.getYangResourcesModuleReferences('ch-1').empty
124
125         cleanup: 'deregister CM handle'
126             deregisterCmHandle(DMI1_URL, 'ch-1')
127     }
128
129     def 'Create a CM-handle with existing moduleSetTag.'() {
130         given: 'DMI will return modules when requested'
131             dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M3']]
132         and: 'existing CM-handles cm-1 with moduleSetTag "A", and cm-2 with moduleSetTag "B"'
133             registerCmHandle(DMI1_URL, 'ch-1', 'A')
134             registerCmHandle(DMI1_URL, 'ch-2', 'B')
135
136         when: 'a CM-handle is registered for creation with moduleSetTag "B"'
137             def cmHandleToCreate = new NcmpServiceCmHandle(cmHandleId: 'ch-3', moduleSetTag: 'B')
138             objectUnderTest.updateDmiRegistration(new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: [cmHandleToCreate]))
139
140         and: 'the module sync watchdog is triggered'
141             moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
142
143         then: 'the CM-handle goes to READY state'
144             assert CmHandleState.READY == objectUnderTest.getCmHandleCompositeState('ch-3').cmHandleState
145
146         and: 'the CM-handle has expected moduleSetTag'
147             assert objectUnderTest.getNcmpServiceCmHandle('ch-3').moduleSetTag == 'B'
148
149         and: 'the CM-handle has expected modules from module set "B": M1 and M3'
150             assert ['M1', 'M3'] == objectUnderTest.getYangResourcesModuleReferences('ch-3').moduleName.sort()
151
152         cleanup: 'deregister CM handles'
153             deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2', 'ch-3'])
154     }
155
156     def 'Create CM-handles with alternate IDs.'() {
157         given: 'DMI will return modules for all CM-handles when requested'
158             dmiDispatcher1.moduleNamesPerCmHandleId = (1..7).collectEntries{ ['ch-'+it, ['M1']] }
159         and: 'an existing CM-handle with an alternate ID'
160             registerCmHandle(DMI1_URL, 'ch-1', NO_MODULE_SET_TAG, 'existing-alt-id')
161         and: 'an existing CM-handle with no alternate ID'
162             registerCmHandle(DMI1_URL, 'ch-2', NO_MODULE_SET_TAG, NO_ALTERNATE_ID)
163
164         when: 'a batch of CM-handles is registered for creation with various alternate IDs'
165             def cmHandlesToCreate = [
166                 new NcmpServiceCmHandle(cmHandleId: 'ch-3', alternateId: NO_ALTERNATE_ID),
167                 new NcmpServiceCmHandle(cmHandleId: 'ch-4', alternateId: 'unique-alt-id'),
168                 new NcmpServiceCmHandle(cmHandleId: 'ch-5', alternateId: 'existing-alt-id'),
169                 new NcmpServiceCmHandle(cmHandleId: 'ch-6', alternateId: 'duplicate-alt-id'),
170                 new NcmpServiceCmHandle(cmHandleId: 'ch-7', alternateId: 'duplicate-alt-id'),
171             ]
172             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate)
173             def dmiPluginRegistrationResponse = objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
174
175         then: 'registration gives expected responses'
176             assert dmiPluginRegistrationResponse.createdCmHandles.sort { it.cmHandle } == [
177                 CmHandleRegistrationResponse.createSuccessResponse('ch-3'),
178                 CmHandleRegistrationResponse.createSuccessResponse('ch-4'),
179                 CmHandleRegistrationResponse.createFailureResponse('ch-5', NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST),
180                 CmHandleRegistrationResponse.createSuccessResponse('ch-6'),
181                 CmHandleRegistrationResponse.createFailureResponse('ch-7', NcmpResponseStatus.CM_HANDLE_ALREADY_EXIST),
182             ]
183
184         cleanup: 'deregister CM handles'
185             deregisterCmHandles(DMI1_URL, (1..7).collect{ 'ch-'+it })
186     }
187
188     def 'CM Handle retry after failed module sync.'() {
189         given: 'DMI is not initially available to handle requests'
190             dmiDispatcher1.isAvailable = false
191
192         when: 'CM-handles are registered for creation'
193             def cmHandlesToCreate = [new NcmpServiceCmHandle(cmHandleId: 'ch-1'), new NcmpServiceCmHandle(cmHandleId: 'ch-2')]
194             def dmiPluginRegistration = new DmiPluginRegistration(dmiPlugin: DMI1_URL, createdCmHandles: cmHandlesToCreate)
195             objectUnderTest.updateDmiRegistration(dmiPluginRegistration)
196
197         and: 'the module sync watchdog is triggered'
198             moduleSyncWatchdog.moduleSyncAdvisedCmHandles()
199
200         then: 'CM-handles go to LOCKED state'
201             assert objectUnderTest.getCmHandleCompositeState('ch-1').cmHandleState == CmHandleState.LOCKED
202
203         when: 'DMI is available for retry'
204             dmiDispatcher1.moduleNamesPerCmHandleId = ['ch-1': ['M1', 'M2'], 'ch-2': ['M1', 'M2']]
205             dmiDispatcher1.isAvailable = true
206
207         and: 'the module sync watchdog is triggered TWICE'
208             2.times { moduleSyncWatchdog.moduleSyncAdvisedCmHandles() }
209
210         then: 'Both CM-handles go to READY state'
211             ['ch-1', 'ch-2'].each { cmHandleId ->
212                 assert objectUnderTest.getCmHandleCompositeState(cmHandleId).cmHandleState == CmHandleState.READY
213             }
214
215         and: 'Both CM-handles have expected modules'
216             ['ch-1', 'ch-2'].each { cmHandleId ->
217                 assert objectUnderTest.getYangResourcesModuleReferences(cmHandleId).moduleName.sort() == ['M1', 'M2']
218             }
219
220         cleanup: 'deregister CM handles'
221             deregisterCmHandles(DMI1_URL, ['ch-1', 'ch-2'])
222     }
223
224     def subscribeAndClearPreviousMessages(consumerGroupId, topicName) {
225         kafkaConsumer = KafkaTestContainer.getLegacyEventConsumer(consumerGroupId)
226         kafkaConsumer.subscribe([topicName])
227         kafkaConsumer.poll(Duration.ofMillis(500))
228     }
229
230 }