dd1c1fb571847cf5bdf0125b96a9423f66b90f20
[cps.git] /
1 /*
2  *  ============LICENSE_START=======================================================
3  *  Copyright (C) 2022-2025 Nordix Foundation
4  *  Modifications Copyright (C) 2022 Bell Canada
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *        http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.onap.cps.ncmp.impl.inventory.sync
23
24 import ch.qos.logback.classic.Level
25 import ch.qos.logback.classic.Logger
26 import ch.qos.logback.classic.spi.ILoggingEvent
27 import ch.qos.logback.core.read.ListAppender
28 import com.hazelcast.map.IMap
29 import org.onap.cps.init.actuator.ReadinessManager
30 import org.slf4j.LoggerFactory
31
32 import java.util.concurrent.ArrayBlockingQueue
33 import org.onap.cps.ncmp.impl.inventory.models.YangModelCmHandle
34 import spock.lang.Specification
35
36 class ModuleSyncWatchdogSpec extends Specification {
37
38     def mockModuleOperationsUtils = Mock(ModuleOperationsUtils)
39
40     def static testQueueCapacity = 50 + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE
41
42     def moduleSyncWorkQueue = new ArrayBlockingQueue(testQueueCapacity)
43
44     def mockModuleSyncStartedOnCmHandles = Mock(IMap<String, Object>)
45
46     def mockModuleSyncTasks = Mock(ModuleSyncTasks)
47
48     def mockCpsAndNcmpLock = Mock(IMap<String,String>)
49
50     def mockReadinessManager = Mock(ReadinessManager)
51
52     def objectUnderTest = new ModuleSyncWatchdog(mockModuleOperationsUtils, moduleSyncWorkQueue , mockModuleSyncStartedOnCmHandles, mockModuleSyncTasks, mockCpsAndNcmpLock, mockReadinessManager)
53
54     def logAppender = Spy(ListAppender<ILoggingEvent>)
55
56     void setup() {
57         def logger = LoggerFactory.getLogger(ModuleSyncWatchdog)
58         logger.setLevel(Level.INFO)
59         logger.addAppender(logAppender)
60         logAppender.start()
61     }
62
63     void cleanup() {
64         ((Logger) LoggerFactory.getLogger(ModuleSyncWatchdog.class)).detachAndStopAllAppenders()
65     }
66
67     def 'Module sync watchdog is triggered'(){
68         given: 'the system is not ready to accept traffic'
69             mockReadinessManager.isReady() >> false
70         when: 'module sync is started'
71             objectUnderTest.scheduledModuleSyncAdvisedCmHandles()
72         then: 'an event is logged with level INFO'
73             def loggingEvent = getLoggingEvent()
74             assert loggingEvent.level == Level.INFO
75         and: 'the log indicates that the system is not ready yet'
76             assert loggingEvent.formattedMessage == 'System is not ready yet'
77     }
78
79     def 'Module sync advised cm handles with #scenario.'() {
80         given: 'system is ready to accept traffic'
81             mockReadinessManager.isReady() >> true
82         and: 'module sync utilities returns #numberOfAdvisedCmHandles advised cm handles'
83             mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(numberOfAdvisedCmHandles)
84         and: 'module sync utilities returns no failed (locked) cm handles'
85             mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> []
86         and: 'the work queue can be locked'
87             mockCpsAndNcmpLock.tryLock('workQueueLock') >> true
88         when: ' module sync is started'
89             objectUnderTest.moduleSyncAdvisedCmHandles()
90         then: 'it performs #expectedNumberOfTaskExecutions tasks'
91             expectedNumberOfTaskExecutions * mockModuleSyncTasks.performModuleSync(*_)
92         and: 'the executing thread is unlocked'
93             1 * mockCpsAndNcmpLock.unlock('workQueueLock')
94         where: 'the following parameter are used'
95             scenario              | numberOfAdvisedCmHandles                                          || expectedNumberOfTaskExecutions
96             'none at all'         | 0                                                                 || 0
97             'less then 1 batch'   | 1                                                                 || 1
98             'exactly 1 batch'     | ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                         || 1
99             '2 batches'           | 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE                     || 2
100             'queue capacity'      | testQueueCapacity                                                 || 3
101             'over queue capacity' | testQueueCapacity + 2 * ModuleSyncWatchdog.MODULE_SYNC_BATCH_SIZE || 3
102     }
103
104     def 'Module sync cm handles starts with no available threads.'() {
105         given: 'system is ready to accept traffic'
106             mockReadinessManager.isReady() >> true
107         and: 'module sync utilities returns a advise cm handles'
108             mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(1)
109         and: 'the work queue can be locked'
110             mockCpsAndNcmpLock.tryLock('workQueueLock') >> true
111         when: ' module sync is started'
112             objectUnderTest.moduleSyncAdvisedCmHandles()
113         then: 'it performs one task'
114             1 * mockModuleSyncTasks.performModuleSync(*_)
115     }
116
117     def 'Module sync advised cm handle already handled by other thread.'() {
118         given: 'system is ready to accept traffic'
119             mockReadinessManager.isReady() >> true
120         and: 'module sync utilities returns an advised cm handle'
121             mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(1)
122         and: 'the work queue can be locked'
123             mockCpsAndNcmpLock.tryLock('workQueueLock') >> true
124         and: 'the semaphore cache indicates the cm handle is already being processed'
125             mockModuleSyncStartedOnCmHandles.putIfAbsent(*_) >> 'Started'
126         when: 'module sync is started'
127             objectUnderTest.moduleSyncAdvisedCmHandles()
128         then: 'it does NOT execute a task to process the (empty) batch'
129             0 * mockModuleSyncTasks.performModuleSync(*_)
130     }
131
132     def 'Module sync with previous cm handle(s) left in work queue.'() {
133         given: 'system is ready to accept traffic'
134             mockReadinessManager.isReady() >> true
135         and: 'there is still a cm handle in the queue'
136             moduleSyncWorkQueue.offer('ch-1')
137         when: 'module sync is started'
138             objectUnderTest.moduleSyncAdvisedCmHandles()
139         then: 'it does executes only one task to process the remaining handle in the queue'
140             1 * mockModuleSyncTasks.performModuleSync(*_)
141     }
142
143     def 'Reset failed cm handles.'() {
144         given: 'system is ready to accept traffic'
145             mockReadinessManager.isReady() >> true
146         and: 'module sync utilities returns failed cm handles'
147             def failedCmHandles = [new YangModelCmHandle()]
148             mockModuleOperationsUtils.getCmHandlesThatFailedModelSyncOrUpgrade() >> failedCmHandles
149         when: 'reset failed cm handles is started'
150             objectUnderTest.setPreviouslyLockedCmHandlesToAdvised()
151         then: 'it is delegated to the module sync task (service)'
152             1 * mockModuleSyncTasks.setCmHandlesToAdvised(failedCmHandles)
153     }
154
155     def 'Module Sync Locking.'() {
156         given: 'system is ready to accept traffic'
157             mockReadinessManager.isReady() >> true
158         and: 'module sync utilities returns an advised cm handle'
159             mockModuleOperationsUtils.getAdvisedCmHandleIds() >> createCmHandleIds(1)
160         and: 'can be locked is : #canLock'
161             mockCpsAndNcmpLock.tryLock('workQueueLock') >> canLock
162         when: 'attempt to populate the work queue'
163             objectUnderTest.populateWorkQueueIfNeeded()
164         then: 'the queue remains empty is #expectQueueRemainsEmpty'
165             assert moduleSyncWorkQueue.isEmpty() == expectQueueRemainsEmpty
166         and: 'unlock is called only when thread is able to enter the critical section'
167             expectedInvocationToUnlock * mockCpsAndNcmpLock.unlock('workQueueLock')
168         where: 'the following lock states are applied'
169             canLock || expectQueueRemainsEmpty || expectedInvocationToUnlock
170             false   || true                    || 0
171             true    || false                   || 1
172     }
173
174     def createCmHandleIds(numberOfCmHandles) {
175         return (numberOfCmHandles > 0) ? (1..numberOfCmHandles).collect { 'ch-'+it } : []
176     }
177
178     def getLoggingEvent() {
179         return logAppender.list[0]
180     }
181 }