2121import com .cloud .network .dao .NetworkDao ;
2222import com .cloud .network .dao .NetworkVO ;
2323import com .cloud .storage .Volume ;
24+ import com .cloud .utils .db .SearchCriteria ;
2425import com .cloud .utils .fsm .NoTransitionException ;
2526import com .cloud .vm .VMInstanceVO ;
2627import com .cloud .vm .VirtualMachine ;
3031import org .apache .cloudstack .engine .orchestration .service .NetworkOrchestrationService ;
3132import org .apache .cloudstack .engine .subsystem .api .storage .VolumeDataFactory ;
3233import org .apache .cloudstack .engine .subsystem .api .storage .VolumeInfo ;
34+ import org .apache .cloudstack .framework .jobs .dao .AsyncJobDao ;
3335import org .junit .Test ;
3436import org .junit .runner .RunWith ;
37+ import org .mockito .InOrder ;
3538import org .mockito .InjectMocks ;
3639import org .mockito .Mock ;
3740import org .mockito .Mockito ;
3841import org .mockito .Spy ;
3942import org .mockito .junit .MockitoJUnitRunner ;
4043
44+ import java .util .Collections ;
45+ import java .util .Date ;
46+ import java .util .List ;
47+
4148import static org .mockito .Mockito .when ;
4249
4350@ RunWith (MockitoJUnitRunner .class )
@@ -55,6 +62,29 @@ public class AsyncJobManagerImplTest {
5562 NetworkDao networkDao ;
5663 @ Mock
5764 NetworkOrchestrationService networkOrchestrationService ;
65+ @ Mock
66+ AsyncJobDao asyncJobDao ;
67+
68+ private static final long NETWORK_ID = 42L ;
69+ private static final long ACCOUNT_ID = 2L ;
70+ // job.expire.minutes defaults to one day; these networks were created two days ago
71+ private static final Date OLD_NETWORK_CREATED = new Date (System .currentTimeMillis () - 2 * 24 * 60 * 60 * 1000L );
72+
73+ private NetworkVO mockImplementingNetworkListed (Date created ) {
74+ NetworkVO listed = Mockito .mock (NetworkVO .class );
75+ when (listed .getCreated ()).thenReturn (created );
76+ SearchCriteria <NetworkVO > sc = Mockito .mock (SearchCriteria .class );
77+ when (networkDao .createSearchCriteria ()).thenReturn (sc );
78+ when (networkDao .search (sc , null )).thenReturn (List .of (listed ));
79+ return listed ;
80+ }
81+
82+ private NetworkVO mockNetworkReadUnderLock (Network .State state ) {
83+ NetworkVO locked = Mockito .mock (NetworkVO .class );
84+ when (locked .getState ()).thenReturn (state );
85+ when (networkDao .acquireInLockTable (NETWORK_ID , 0 )).thenReturn (locked );
86+ return locked ;
87+ }
5888
5989 @ Test
6090 public void testCleanupVolumeResource () {
@@ -93,4 +123,99 @@ public void testCleanupNetworkResource() throws NoTransitionException {
93123 Mockito .verify (networkOrchestrationService , Mockito .times (1 )).stateTransitTo (networkVO ,
94124 Network .Event .OperationFailed );
95125 }
126+
127+ @ Test
128+ public void testCleanupOrphanedNetworksSkipsNetworkWhoseLockIsHeld () throws NoTransitionException {
129+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
130+ when (listed .getId ()).thenReturn (NETWORK_ID );
131+ // a VM start implementing the network holds its lock, so a try without waiting fails
132+ when (networkDao .acquireInLockTable (NETWORK_ID , 0 )).thenReturn (null );
133+
134+ asyncJobManager .cleanupOrphanedNetworks ();
135+
136+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
137+ Mockito .verify (networkDao , Mockito .never ()).update (Mockito .anyLong (), Mockito .any ());
138+ Mockito .verify (networkDao , Mockito .never ()).releaseFromLockTable (Mockito .anyLong ());
139+ Mockito .verifyNoInteractions (asyncJobDao );
140+ }
141+
142+ @ Test
143+ public void testCleanupOrphanedNetworksShutsDownOrphanedNetworkUnderLock () throws NoTransitionException {
144+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
145+ when (listed .getId ()).thenReturn (NETWORK_ID );
146+ NetworkVO locked = mockNetworkReadUnderLock (Network .State .Implementing );
147+ when (locked .getId ()).thenReturn (NETWORK_ID );
148+ when (locked .getAccountId ()).thenReturn (ACCOUNT_ID );
149+ when (locked .getCreated ()).thenReturn (OLD_NETWORK_CREATED );
150+ when (asyncJobDao .findInstancePendingAsyncJobs ("Network" , ACCOUNT_ID )).thenReturn (Collections .emptyList ());
151+
152+ asyncJobManager .cleanupOrphanedNetworks ();
153+
154+ InOrder inOrder = Mockito .inOrder (networkDao , networkOrchestrationService );
155+ inOrder .verify (networkDao ).acquireInLockTable (NETWORK_ID , 0 );
156+ inOrder .verify (networkOrchestrationService ).stateTransitTo (locked , Network .Event .OperationFailed );
157+ inOrder .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
158+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .eq (listed ), Mockito .any ());
159+ }
160+
161+ @ Test
162+ public void testCleanupOrphanedNetworksSkipsNetworkImplementedBeforeItWasLocked () throws NoTransitionException {
163+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
164+ when (listed .getId ()).thenReturn (NETWORK_ID );
165+ // the implementation finished between the search and the lock
166+ mockNetworkReadUnderLock (Network .State .Implemented );
167+
168+ asyncJobManager .cleanupOrphanedNetworks ();
169+
170+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
171+ Mockito .verify (networkDao , Mockito .never ()).update (Mockito .anyLong (), Mockito .any ());
172+ Mockito .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
173+ }
174+
175+ @ Test
176+ public void testCleanupOrphanedNetworksSkipsNetworkWithPendingNetworkJob () throws NoTransitionException {
177+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
178+ when (listed .getId ()).thenReturn (NETWORK_ID );
179+ NetworkVO locked = mockNetworkReadUnderLock (Network .State .Implementing );
180+ when (locked .getId ()).thenReturn (NETWORK_ID );
181+ when (locked .getAccountId ()).thenReturn (ACCOUNT_ID );
182+ AsyncJobVO job = new AsyncJobVO ();
183+ job .setInstanceId (NETWORK_ID );
184+ when (asyncJobDao .findInstancePendingAsyncJobs ("Network" , ACCOUNT_ID )).thenReturn (List .of (job ));
185+
186+ asyncJobManager .cleanupOrphanedNetworks ();
187+
188+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
189+ Mockito .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
190+ }
191+
192+ @ Test
193+ public void testCleanupOrphanedNetworksSkipsYoungNetwork () throws NoTransitionException {
194+ mockImplementingNetworkListed (new Date ());
195+
196+ asyncJobManager .cleanupOrphanedNetworks ();
197+
198+ Mockito .verify (networkDao , Mockito .never ()).acquireInLockTable (Mockito .anyLong (), Mockito .anyInt ());
199+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
200+ Mockito .verifyNoInteractions (asyncJobDao );
201+ }
202+
203+ @ Test
204+ public void testCleanupOrphanedNetworksReleasesLockWhenForcingShutdown () throws NoTransitionException {
205+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
206+ when (listed .getId ()).thenReturn (NETWORK_ID );
207+ NetworkVO locked = mockNetworkReadUnderLock (Network .State .Implementing );
208+ when (locked .getId ()).thenReturn (NETWORK_ID );
209+ when (locked .getAccountId ()).thenReturn (ACCOUNT_ID );
210+ when (locked .getCreated ()).thenReturn (OLD_NETWORK_CREATED );
211+ when (asyncJobDao .findInstancePendingAsyncJobs ("Network" , ACCOUNT_ID )).thenReturn (Collections .emptyList ());
212+ when (networkOrchestrationService .stateTransitTo (locked , Network .Event .OperationFailed )).thenThrow (new NoTransitionException ("no transition" ));
213+
214+ asyncJobManager .cleanupOrphanedNetworks ();
215+
216+ InOrder inOrder = Mockito .inOrder (locked , networkDao );
217+ inOrder .verify (locked ).setState (Network .State .Shutdown );
218+ inOrder .verify (networkDao ).update (NETWORK_ID , locked );
219+ inOrder .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
220+ }
96221}
0 commit comments