2727import com .cloud .network .dao .NetworkVO ;
2828import com .cloud .storage .Volume ;
2929import com .cloud .utils .db .DbProperties ;
30+ import com .cloud .utils .db .SearchCriteria ;
3031import com .cloud .utils .fsm .NoTransitionException ;
3132import com .cloud .vm .VMInstanceVO ;
3233import com .cloud .vm .VirtualMachine ;
3839import org .apache .cloudstack .engine .subsystem .api .storage .VolumeDataFactory ;
3940import org .apache .cloudstack .engine .subsystem .api .storage .VolumeInfo ;
4041import org .apache .cloudstack .framework .config .ConfigKey ;
42+ import org .apache .cloudstack .framework .jobs .dao .AsyncJobDao ;
4143import org .junit .After ;
4244import org .junit .Assert ;
4345import org .junit .Before ;
4446import org .junit .Test ;
4547import org .junit .runner .RunWith ;
48+ import org .mockito .InOrder ;
4649import org .mockito .InjectMocks ;
4750import org .mockito .Mock ;
4851import org .mockito .Mockito ;
4952import org .mockito .Spy ;
5053import org .mockito .junit .MockitoJUnitRunner ;
5154
55+ import java .util .Collections ;
56+ import java .util .Date ;
57+ import java .util .List ;
58+
5259import static org .mockito .Mockito .when ;
5360
5461@ RunWith (MockitoJUnitRunner .class )
@@ -72,6 +79,29 @@ public class AsyncJobManagerImplTest {
7279 NetworkDao networkDao ;
7380 @ Mock
7481 NetworkOrchestrationService networkOrchestrationService ;
82+ @ Mock
83+ AsyncJobDao asyncJobDao ;
84+
85+ private static final long NETWORK_ID = 42L ;
86+ private static final long ACCOUNT_ID = 2L ;
87+ // job.expire.minutes defaults to one day; these networks were created two days ago
88+ private static final Date OLD_NETWORK_CREATED = new Date (System .currentTimeMillis () - 2 * 24 * 60 * 60 * 1000L );
89+
90+ private NetworkVO mockImplementingNetworkListed (Date created ) {
91+ NetworkVO listed = Mockito .mock (NetworkVO .class );
92+ when (listed .getCreated ()).thenReturn (created );
93+ SearchCriteria <NetworkVO > sc = Mockito .mock (SearchCriteria .class );
94+ when (networkDao .createSearchCriteria ()).thenReturn (sc );
95+ when (networkDao .search (sc , null )).thenReturn (List .of (listed ));
96+ return listed ;
97+ }
98+
99+ private NetworkVO mockNetworkReadUnderLock (Network .State state ) {
100+ NetworkVO locked = Mockito .mock (NetworkVO .class );
101+ when (locked .getState ()).thenReturn (state );
102+ when (networkDao .acquireInLockTable (NETWORK_ID , 0 )).thenReturn (locked );
103+ return locked ;
104+ }
75105
76106 @ Before
77107 public void preloadDbProperties () throws Exception {
@@ -135,6 +165,101 @@ public void testCleanupNetworkResource() throws NoTransitionException {
135165 Network .Event .OperationFailed );
136166 }
137167
168+ @ Test
169+ public void testCleanupOrphanedNetworksSkipsNetworkWhoseLockIsHeld () throws NoTransitionException {
170+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
171+ when (listed .getId ()).thenReturn (NETWORK_ID );
172+ // a VM start implementing the network holds its lock, so a try without waiting fails
173+ when (networkDao .acquireInLockTable (NETWORK_ID , 0 )).thenReturn (null );
174+
175+ asyncJobManager .cleanupOrphanedNetworks ();
176+
177+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
178+ Mockito .verify (networkDao , Mockito .never ()).update (Mockito .anyLong (), Mockito .any ());
179+ Mockito .verify (networkDao , Mockito .never ()).releaseFromLockTable (Mockito .anyLong ());
180+ Mockito .verifyNoInteractions (asyncJobDao );
181+ }
182+
183+ @ Test
184+ public void testCleanupOrphanedNetworksShutsDownOrphanedNetworkUnderLock () throws NoTransitionException {
185+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
186+ when (listed .getId ()).thenReturn (NETWORK_ID );
187+ NetworkVO locked = mockNetworkReadUnderLock (Network .State .Implementing );
188+ when (locked .getId ()).thenReturn (NETWORK_ID );
189+ when (locked .getAccountId ()).thenReturn (ACCOUNT_ID );
190+ when (locked .getCreated ()).thenReturn (OLD_NETWORK_CREATED );
191+ when (asyncJobDao .findInstancePendingAsyncJobs ("Network" , ACCOUNT_ID )).thenReturn (Collections .emptyList ());
192+
193+ asyncJobManager .cleanupOrphanedNetworks ();
194+
195+ InOrder inOrder = Mockito .inOrder (networkDao , networkOrchestrationService );
196+ inOrder .verify (networkDao ).acquireInLockTable (NETWORK_ID , 0 );
197+ inOrder .verify (networkOrchestrationService ).stateTransitTo (locked , Network .Event .OperationFailed );
198+ inOrder .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
199+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .eq (listed ), Mockito .any ());
200+ }
201+
202+ @ Test
203+ public void testCleanupOrphanedNetworksSkipsNetworkImplementedBeforeItWasLocked () throws NoTransitionException {
204+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
205+ when (listed .getId ()).thenReturn (NETWORK_ID );
206+ // the implementation finished between the search and the lock
207+ mockNetworkReadUnderLock (Network .State .Implemented );
208+
209+ asyncJobManager .cleanupOrphanedNetworks ();
210+
211+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
212+ Mockito .verify (networkDao , Mockito .never ()).update (Mockito .anyLong (), Mockito .any ());
213+ Mockito .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
214+ }
215+
216+ @ Test
217+ public void testCleanupOrphanedNetworksSkipsNetworkWithPendingNetworkJob () throws NoTransitionException {
218+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
219+ when (listed .getId ()).thenReturn (NETWORK_ID );
220+ NetworkVO locked = mockNetworkReadUnderLock (Network .State .Implementing );
221+ when (locked .getId ()).thenReturn (NETWORK_ID );
222+ when (locked .getAccountId ()).thenReturn (ACCOUNT_ID );
223+ AsyncJobVO job = new AsyncJobVO ();
224+ job .setInstanceId (NETWORK_ID );
225+ when (asyncJobDao .findInstancePendingAsyncJobs ("Network" , ACCOUNT_ID )).thenReturn (List .of (job ));
226+
227+ asyncJobManager .cleanupOrphanedNetworks ();
228+
229+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
230+ Mockito .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
231+ }
232+
233+ @ Test
234+ public void testCleanupOrphanedNetworksSkipsYoungNetwork () throws NoTransitionException {
235+ mockImplementingNetworkListed (new Date ());
236+
237+ asyncJobManager .cleanupOrphanedNetworks ();
238+
239+ Mockito .verify (networkDao , Mockito .never ()).acquireInLockTable (Mockito .anyLong (), Mockito .anyInt ());
240+ Mockito .verify (networkOrchestrationService , Mockito .never ()).stateTransitTo (Mockito .any (), Mockito .any ());
241+ Mockito .verifyNoInteractions (asyncJobDao );
242+ }
243+
244+ @ Test
245+ public void testCleanupOrphanedNetworksReleasesLockWhenForcingShutdown () throws NoTransitionException {
246+ NetworkVO listed = mockImplementingNetworkListed (OLD_NETWORK_CREATED );
247+ when (listed .getId ()).thenReturn (NETWORK_ID );
248+ NetworkVO locked = mockNetworkReadUnderLock (Network .State .Implementing );
249+ when (locked .getId ()).thenReturn (NETWORK_ID );
250+ when (locked .getAccountId ()).thenReturn (ACCOUNT_ID );
251+ when (locked .getCreated ()).thenReturn (OLD_NETWORK_CREATED );
252+ when (asyncJobDao .findInstancePendingAsyncJobs ("Network" , ACCOUNT_ID )).thenReturn (Collections .emptyList ());
253+ when (networkOrchestrationService .stateTransitTo (locked , Network .Event .OperationFailed )).thenThrow (new NoTransitionException ("no transition" ));
254+
255+ asyncJobManager .cleanupOrphanedNetworks ();
256+
257+ InOrder inOrder = Mockito .inOrder (locked , networkDao );
258+ inOrder .verify (locked ).setState (Network .State .Shutdown );
259+ inOrder .verify (networkDao ).update (NETWORK_ID , locked );
260+ inOrder .verify (networkDao ).releaseFromLockTable (NETWORK_ID );
261+ }
262+
138263 @ Test
139264 public void configureApiPoolUsesConfiguredValueWhenLargerThanDbDerived () throws Exception {
140265 overrideConfigValue (AsyncJobManagerImpl .ApiJobPoolSize , 3000 );
0 commit comments