80b5da2b0902a44afce331d3bab9eaa85964d003
[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.ncmp.impl.datajobs.subscription.ncmp
22
23 import org.onap.cps.ncmp.impl.datajobs.subscription.client_to_ncmp.DataSelector
24 import org.onap.cps.ncmp.impl.datajobs.subscription.dmi.DmiInEventMapper
25 import org.onap.cps.ncmp.impl.datajobs.subscription.dmi.EventProducer
26 import org.onap.cps.ncmp.impl.datajobs.subscription.ncmp_to_dmi.DataJobSubscriptionDmiInEvent
27 import org.onap.cps.ncmp.impl.datajobs.subscription.utils.CmDataJobSubscriptionPersistenceService
28 import org.onap.cps.ncmp.impl.inventory.InventoryPersistence
29 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
30 import org.onap.cps.ncmp.impl.utils.AlternateIdMatcher
31 import org.onap.cps.ncmp.impl.utils.JexParser
32 import spock.lang.Specification
33
34 import static org.onap.cps.ncmp.impl.datajobs.subscription.models.CmSubscriptionStatus.ACCEPTED
35
36 class CmSubscriptionHandlerImplSpec extends Specification {
37
38     def mockCmSubscriptionPersistenceService = Mock(CmDataJobSubscriptionPersistenceService)
39     def mockDmiInEventMapper = Mock(DmiInEventMapper)
40     def mockDmiInEventProducer = Mock(EventProducer)
41     def mockInventoryPersistence = Mock(InventoryPersistence)
42     def mockAlternateIdMatcher = Mock(AlternateIdMatcher)
43
44     def objectUnderTest = new CmSubscriptionHandlerImpl(mockCmSubscriptionPersistenceService, mockDmiInEventMapper,
45             mockDmiInEventProducer, mockInventoryPersistence, mockAlternateIdMatcher)
46
47     def 'Process subscription CREATE request for new target [non existing]'() {
48         given: 'relevant subscription details'
49             def mySubId = 'dataJobId'
50             def myDataNodeSelectors = ['/parent[id="1"]'].toList()
51             def notificationTypes = []
52             def notificationFilter = ''
53             def dataSelector = new DataSelector(notificationTypes: notificationTypes, notificationFilter: notificationFilter)
54         and: 'alternate Id matcher returns cm handle id for given data node selector'
55             def fdn = getFdn(myDataNodeSelectors.iterator().next())
56             mockAlternateIdMatcher.getCmHandleId(fdn) >> 'myCmHandleId'
57         and: 'the persistence service returns inactive data node selector(s)'
58             mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(mySubId) >> ['/parent[id="1"]']
59         and: 'the inventory persistence service returns cm handle'
60             mockInventoryPersistence.getYangModelCmHandle('myCmHandleId') >> new YangModelCmHandle(dmiServiceName: 'myDmiService')
61         and: 'DMI in event mapper returns event'
62             def myDmiInEvent = new DataJobSubscriptionDmiInEvent()
63             mockDmiInEventMapper.toDmiInEvent(['myCmHandleId'], myDataNodeSelectors, notificationTypes, notificationFilter) >> myDmiInEvent
64         when: 'a subscription is created'
65             objectUnderTest.createSubscription(dataSelector, mySubId, myDataNodeSelectors)
66         then: 'each datanode selector is added using the persistence service'
67             1 * mockCmSubscriptionPersistenceService.add(mySubId, '/parent[id="1"]')
68         and: 'an event is sent to the correct DMI'
69             1 * mockDmiInEventProducer.send(mySubId, 'myDmiService', 'subscriptionCreateRequest', _)
70     }
71
72     def 'Process subscription CREATE request for new targets [non existing] to be sent to multiple DMIs'() {
73         given: 'relevant subscription details'
74             def mySubId = 'dataJobId'
75             def myDataNodeSelectors = [
76                     '/parent[id="forDmi1"]',
77                     '/parent[id="forDmi1"]/child',
78                     '/parent[id="forDmi2"]'].toList()
79             def notificationTypes = []
80             def notificationFilter = ''
81             def dataSelector = new DataSelector(notificationTypes: notificationTypes, notificationFilter: notificationFilter)
82         and: 'alternate Id matcher returns cm handle ids for given data node selectors'
83             def fdn1 = getFdn(myDataNodeSelectors[0])
84             def fdn2 = getFdn(myDataNodeSelectors[1])
85             def fdn3 = getFdn(myDataNodeSelectors[2])
86             mockAlternateIdMatcher.getCmHandleId(fdn1) >> 'myCmHandleId1'
87             mockAlternateIdMatcher.getCmHandleId(fdn2) >> 'myCmHandleId1'
88             mockAlternateIdMatcher.getCmHandleId(fdn3) >> 'myCmHandleId2'
89         and: 'the persistence service returns inactive data node selector(s)'
90             mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(mySubId) >> [
91                     '/parent[id="forDmi1"]',
92                     '/parent[id="forDmi1"]/child',
93                     '/parent[id="forDmi2"]']
94         and: 'the inventory persistence service returns cm handles with dmi information'
95             mockInventoryPersistence.getYangModelCmHandle('myCmHandleId1') >> new YangModelCmHandle(dmiServiceName: 'myDmiService1')
96             mockInventoryPersistence.getYangModelCmHandle('myCmHandleId2') >> new YangModelCmHandle(dmiServiceName: 'myDmiService2')
97         and: 'DMI in event mapper returns events'
98             def myDmiInEvent1 = new DataJobSubscriptionDmiInEvent()
99             def myDmiInEvent2 = new DataJobSubscriptionDmiInEvent()
100             mockDmiInEventMapper.toDmiInEvent(['myCmHandleId1'], ['/parent[id="forDmi1"]', '/parent[id="forDmi1"]/child'], notificationTypes, notificationFilter) >> myDmiInEvent1
101             mockDmiInEventMapper.toDmiInEvent(['myCmHandleId2'], ['/parent[id="forDmi2"]'], notificationTypes, notificationFilter) >> myDmiInEvent2
102         when: 'a subscription is created'
103             objectUnderTest.createSubscription(dataSelector, mySubId, myDataNodeSelectors)
104         then: 'each datanode selector is added using the persistence service'
105             myDataNodeSelectors.each { dataNodeSelector ->
106                 1 * mockCmSubscriptionPersistenceService.add(_, dataNodeSelector)
107             }
108         and: 'an event is sent to each DMI involved'
109             1 * mockDmiInEventProducer.send(mySubId, 'myDmiService1', 'subscriptionCreateRequest', myDmiInEvent1)
110             1 * mockDmiInEventProducer.send(mySubId, 'myDmiService2', 'subscriptionCreateRequest', myDmiInEvent2)
111     }
112
113     def 'Process subscription CREATE request for overlapping targets [non existing & existing]'() {
114         given: 'relevant subscription details'
115             def myNewSubId = 'newId'
116             def myDataNodeSelectors = ['/newDataNodeSelector[id=""]'].toList()
117             def dataSelector = new DataSelector(notificationTypes: [], notificationFilter: '')
118         and: 'alternate id matcher always returns a cm handle id'
119             mockAlternateIdMatcher.getCmHandleId(_) >> 'someCmHandleId'
120         and: 'the inventory persistence service returns cm handles with dmi information'
121             mockInventoryPersistence.getYangModelCmHandle(_) >> new YangModelCmHandle(dmiServiceName: 'myDmiService')
122         and: 'the inventory persistence service returns inactive data node selector(s)'
123             mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(myNewSubId) >> inactiveDataNodeSelectors
124         when: 'a subscription is created'
125             objectUnderTest.createSubscription(dataSelector, myNewSubId, myDataNodeSelectors)
126         then: 'each datanode selector is added using the persistence service'
127             1 * mockCmSubscriptionPersistenceService.add(_, myDataNodeSelectors.iterator().next())
128         and: 'an event is sent to each DMI involved'
129             expectedCallsToDmi * mockDmiInEventProducer.send(myNewSubId, 'myDmiService', 'subscriptionCreateRequest', _)
130         where: 'following data are used'
131             scenario                                            | inactiveDataNodeSelectors                                           || expectedCallsToDmi
132             'new target overlaps with ACCEPTED targets'         | []                                                                  || 0
133             'new target overlaps with REJECTED targets'         | ['/existingDataNodeSelector[id=""]', '/newDataNodeSelector[id=""]'] || 1
134             'new target overlaps with UNKNOWN targets'          | ['/existingDataNodeSelector[id=""]', '/newDataNodeSelector[id=""]'] || 1
135             'new target does not overlap with existing targets' | ['/newDataNodeSelector[id=""]']                                     || 1
136     }
137
138     def 'Process subscription DELETE request where all data node selectors become unused'() {
139         given: 'a subscription id and its associated data node selectors'
140             def mySubId = 'deleteJobId'
141             def myDataNodeSelector = ['/node[id="1"]']
142         and: 'the persistence service returns the data node selectors'
143             mockCmSubscriptionPersistenceService.getDataNodeSelectors(mySubId) >> myDataNodeSelector
144         and: 'no other subscriptions exist for the data node selectors'
145             mockCmSubscriptionPersistenceService.getSubscriptionIds('/node[id="1"]') >> []
146         and: 'cm handle resolution setup'
147             def fdn = getFdn('/node[id="1"]')
148             mockAlternateIdMatcher.getCmHandleId(fdn) >> 'cmHandleId1'
149             mockInventoryPersistence.getYangModelCmHandle('cmHandleId1') >> new YangModelCmHandle(dmiServiceName: 'dmiService1')
150         and: 'DMI in event mapper returns events'
151             def deleteEvent = new DataJobSubscriptionDmiInEvent()
152             mockDmiInEventMapper.toDmiInEvent(['cmHandleId1'], ['/node[id="1"]'], null, null) >> deleteEvent
153         when: 'a subscription is deleted'
154             objectUnderTest.deleteSubscription(mySubId)
155         then: 'subscription is removed from persistence'
156             1 * mockCmSubscriptionPersistenceService.delete(mySubId, '/node[id="1"]')
157         and: 'an event is sent to each DMI involved'
158             1 * mockDmiInEventProducer.send(mySubId, 'dmiService1', 'subscriptionDeleteRequest', deleteEvent)
159     }
160
161     def 'Process subscription DELETE request where some data node selectors are still in use'() {
162         given: 'a subscription id and two associated selectors'
163             def mySubId = 'deleteJobId2'
164             def dataNodeSelectors = ['/node[id="1"]', '/node[id="2"]']
165         and: 'the persistence service returns the data node selectors'
166             mockCmSubscriptionPersistenceService.getDataNodeSelectors(mySubId) >> dataNodeSelectors
167         and: 'data node selector 1 has no more subscribers, data node selector 2 still has subscribers'
168             mockCmSubscriptionPersistenceService.getSubscriptionIds('/node[id="1"]') >> []
169             mockCmSubscriptionPersistenceService.getSubscriptionIds('/node[id="2"]') >> ['anotherSub']
170         and: 'cm handle resolution for data node selector 1'
171             def fdn = getFdn('/node[id="1"]')
172             mockAlternateIdMatcher.getCmHandleId(fdn) >> 'cmHandleIdX'
173             mockInventoryPersistence.getYangModelCmHandle('cmHandleIdX') >> new YangModelCmHandle(dmiServiceName: 'dmiServiceX')
174         and: 'DMI in event mapper returns events'
175             def deleteEvent = new DataJobSubscriptionDmiInEvent()
176             mockDmiInEventMapper.toDmiInEvent(['cmHandleIdX'], ['/node[id="1"]'], null, null) >> deleteEvent
177         when: 'a subscription is deleted'
178             objectUnderTest.deleteSubscription(mySubId)
179         then: 'subscription is removed from persistence for both data node selectors'
180             1 * mockCmSubscriptionPersistenceService.delete(mySubId, '/node[id="1"]')
181             1 * mockCmSubscriptionPersistenceService.delete(mySubId, '/node[id="2"]')
182         and: 'delete event is sent only for data node selectors without any subscriber'
183             1 * mockDmiInEventProducer.send(mySubId, 'dmiServiceX', 'subscriptionDeleteRequest', deleteEvent)
184     }
185
186     def 'Process subscription DELETE request where cmHandleId cannot be resolved'() {
187         given: 'a subscription id and its data node selector'
188             def mySubId = 'deleteJobId3'
189             def dataNodeSelectors = ['/node[id="unresolvable"]']
190         and: 'the persistence service returns the data node selectors'
191             mockCmSubscriptionPersistenceService.getDataNodeSelectors(mySubId) >> dataNodeSelectors
192         and: 'no more subscriptions exist for the data node selector'
193             mockCmSubscriptionPersistenceService.getSubscriptionIds('/node[id="unresolvable"]') >> []
194         and: 'alternate id matcher cannot resolve cm handle id'
195             def fdn = getFdn('/node[id="unresolvable"]')
196             mockAlternateIdMatcher.getCmHandleId(fdn) >> null
197         and: 'DMI in event mapper returns events'
198             def deleteEvent = new DataJobSubscriptionDmiInEvent()
199             mockDmiInEventMapper.toDmiInEvent(['cmHandleIdX'], ['/node[id="1"]'], null, null) >> deleteEvent
200         when: 'a subscription is deleted'
201             objectUnderTest.deleteSubscription(mySubId)
202         then: 'subscription is removed from persistence'
203             1 * mockCmSubscriptionPersistenceService.delete(mySubId, '/node[id="unresolvable"]')
204         and: 'no delete event is sent because cmHandleId was not resolved'
205             0 * mockDmiInEventProducer.send(*_)
206     }
207
208     def 'Update subscription status to ACCEPTED: #scenario'() {
209         given: 'a subscription ID'
210             def mySubscriptionId = 'mySubId'
211         and: 'the persistence service returns all inactive data node selectors'
212             def myDataNodeSelectors = ['/myDataNodeSelector[id=""]'].asList()
213             mockCmSubscriptionPersistenceService.getInactiveDataNodeSelectors(mySubscriptionId) >> myDataNodeSelectors
214         and: 'alternate id matcher always returns a cm handle id'
215             mockAlternateIdMatcher.getCmHandleId(_) >> 'someCmHandleId'
216         and: 'the inventory persistence service returns a yang model with a dmi service name for the accepted subscription'
217             mockInventoryPersistence.getYangModelCmHandle(_) >> new YangModelCmHandle(dmiServiceName: 'myDmi')
218         when: 'the method to update subscription status is called with status=ACCEPTED and dmi #dmiName'
219             objectUnderTest.updateCmSubscriptionStatus(mySubscriptionId, dmiName, ACCEPTED)
220         then: 'the persistence service to update subscription status is ONLY called for matching dmi name'
221             expectedCallsToPersistenceService * mockCmSubscriptionPersistenceService.updateCmSubscriptionStatus('/myDataNodeSelector[id=""]', ACCEPTED)
222         where: 'the following data are used'
223             scenario                           | dmiName        || expectedCallsToPersistenceService
224             'data node selector for "myDmi"'   | 'myDmi'        || 1
225             'data node selector for other dmi' | 'someOtherDmi' || 0
226     }
227
228
229     def getFdn(dataNodeSelector) {
230         return JexParser.extractFdnPrefix(dataNodeSelector).orElse("")
231     }
232 }