1818package org .apache .cloudstack .framework .jobs .impl ;
1919
2020import java .lang .reflect .Field ;
21+ import java .util .Properties ;
22+ import java .util .concurrent .ExecutorService ;
23+ import java .util .concurrent .ThreadPoolExecutor ;
2124
2225import com .cloud .network .Network ;
2326import com .cloud .network .dao .NetworkDao ;
2427import com .cloud .network .dao .NetworkVO ;
2528import com .cloud .storage .Volume ;
29+ import com .cloud .utils .db .DbProperties ;
2630import com .cloud .utils .fsm .NoTransitionException ;
2731import com .cloud .vm .VMInstanceVO ;
2832import com .cloud .vm .VirtualMachine ;
3438import org .apache .cloudstack .engine .subsystem .api .storage .VolumeDataFactory ;
3539import org .apache .cloudstack .engine .subsystem .api .storage .VolumeInfo ;
3640import org .apache .cloudstack .framework .config .ConfigKey ;
41+ import org .junit .After ;
3742import org .junit .Assert ;
43+ import org .junit .Before ;
3844import org .junit .Test ;
3945import org .junit .runner .RunWith ;
4046import org .mockito .InjectMocks ;
4753
4854@ RunWith (MockitoJUnitRunner .class )
4955public class AsyncJobManagerImplTest {
56+
57+ // db.cloud.maxActive value preloaded into DbProperties for every test in this class.
58+ // Chosen so the db-derived defaults (4000/2 = 2000 for api, 4000*2/3 = 2666 for worker)
59+ // are non-trivial and easy to reason about vs. the configured minimums under test.
60+ private static final int DB_CLOUD_MAX_ACTIVE = 4000 ;
61+
5062 @ Spy
5163 @ InjectMocks
5264 AsyncJobManagerImpl asyncJobManager ;
@@ -61,6 +73,30 @@ public class AsyncJobManagerImplTest {
6173 @ Mock
6274 NetworkOrchestrationService networkOrchestrationService ;
6375
76+ @ Before
77+ public void preloadDbProperties () throws Exception {
78+ Properties props = new Properties ();
79+ props .setProperty ("db.cloud.maxActive" , String .valueOf (DB_CLOUD_MAX_ACTIVE ));
80+ // Bypass DbProperties' internal "loaded" guard so configure() reads our
81+ // properties instead of trying to load a real db.properties file.
82+ setDbPropertiesStatics (props , true );
83+ }
84+
85+ @ After
86+ public void tearDown () throws Exception {
87+ try {
88+ shutdownIfPresent ("_apiJobExecutor" );
89+ shutdownIfPresent ("_workerJobExecutor" );
90+ } finally {
91+ // Reset static state even if executor shutdown throws, so we do not
92+ // pollute later test classes with our stubbed ConfigKey values or
93+ // preloaded DbProperties.
94+ resetConfigValue (AsyncJobManagerImpl .ApiJobPoolSize );
95+ resetConfigValue (AsyncJobManagerImpl .WorkJobPoolSize );
96+ setDbPropertiesStatics (new Properties (), false );
97+ }
98+ }
99+
64100 @ Test
65101 public void testCleanupVolumeResource () {
66102 AsyncJobVO job = new AsyncJobVO ();
@@ -100,39 +136,86 @@ public void testCleanupNetworkResource() throws NoTransitionException {
100136 }
101137
102138 @ Test
103- public void testPoolSizeUsesConfiguredValueWhenLarger () {
104- int configuredSize = 3000 ;
105- int dbDerivedSize = 2000 ;
106- int result = Math .max (configuredSize , dbDerivedSize );
107- Assert .assertEquals (3000 , result );
139+ public void configureApiPoolUsesConfiguredValueWhenLargerThanDbDerived () throws Exception {
140+ overrideConfigValue (AsyncJobManagerImpl .ApiJobPoolSize , 3000 );
141+ overrideConfigValue (AsyncJobManagerImpl .WorkJobPoolSize , 50 );
142+ invokeConfigureAndSwallowPostSetupNpe ();
143+ // apiPoolSize = max(3000, 4000/2) = 3000 (configured wins)
144+ Assert .assertEquals (3000 , apiPoolCoreSize ());
108145 }
109146
110147 @ Test
111- public void testPoolSizeUsesDbDerivedValueWhenLarger () {
112- int configuredSize = 50 ;
113- int dbDerivedSize = 2000 ;
114- int result = Math .max (configuredSize , dbDerivedSize );
115- Assert .assertEquals (2000 , result );
148+ public void configureApiPoolUsesDbDerivedWhenLargerThanConfigured () throws Exception {
149+ overrideConfigValue (AsyncJobManagerImpl .ApiJobPoolSize , 50 );
150+ overrideConfigValue (AsyncJobManagerImpl .WorkJobPoolSize , 50 );
151+ invokeConfigureAndSwallowPostSetupNpe ();
152+ // apiPoolSize = max(50, 4000/2) = 2000 (db-derived wins)
153+ Assert .assertEquals (DB_CLOUD_MAX_ACTIVE / 2 , apiPoolCoreSize ());
116154 }
117155
118156 @ Test
119- public void testPoolSizeDefaultConfigKeyValues () {
157+ public void configureWorkerPoolUsesTwoThirdsFormula () throws Exception {
120158 overrideConfigValue (AsyncJobManagerImpl .ApiJobPoolSize , 50 );
121159 overrideConfigValue (AsyncJobManagerImpl .WorkJobPoolSize , 50 );
122- Assert .assertEquals (Integer .valueOf (50 ), AsyncJobManagerImpl .ApiJobPoolSize .value ());
123- Assert .assertEquals (Integer .valueOf (50 ), AsyncJobManagerImpl .WorkJobPoolSize .value ());
160+ invokeConfigureAndSwallowPostSetupNpe ();
161+ // workPoolSize = max(50, 4000*2/3) = 2666 (db-derived wins)
162+ Assert .assertEquals ((DB_CLOUD_MAX_ACTIVE * 2 ) / 3 , workerPoolCoreSize ());
124163 }
125164
126165 @ Test
127- public void testPoolSizeConfiguredOverridesDbDerived () {
128- overrideConfigValue (AsyncJobManagerImpl .ApiJobPoolSize , 5000 );
129- int dbMaxActive = 4000 ;
130- int dbDerived = dbMaxActive / 2 ;
131- int actual = Math .max (AsyncJobManagerImpl .ApiJobPoolSize .value (), dbDerived );
132- Assert .assertEquals (5000 , actual );
166+ public void configureWorkerPoolUsesConfiguredValueWhenLargerThanDbDerived () throws Exception {
167+ overrideConfigValue (AsyncJobManagerImpl .ApiJobPoolSize , 50 );
168+ overrideConfigValue (AsyncJobManagerImpl .WorkJobPoolSize , 5000 );
169+ invokeConfigureAndSwallowPostSetupNpe ();
170+ // workPoolSize = max(5000, 4000*2/3) = 5000 (configured wins)
171+ Assert .assertEquals (5000 , workerPoolCoreSize ());
172+ }
173+
174+ /**
175+ * Invoke configure() and swallow the NPE it throws when wiring SearchBuilders
176+ * on the un-mocked DAOs after the pool-sizing block. The executors are created
177+ * inside configure()'s try/catch (before the DAO wiring), so pool sizes can
178+ * still be inspected after the failure.
179+ */
180+ private void invokeConfigureAndSwallowPostSetupNpe () {
181+ try {
182+ asyncJobManager .configure (null , null );
183+ } catch (Exception ignored ) {
184+ // expected: DAO wiring after pool-sizing NPEs because we intentionally
185+ // did not mock the DAOs (they are not part of what we are testing).
186+ }
187+ }
188+
189+ private int apiPoolCoreSize () throws Exception {
190+ Object executor = readField ("_apiJobExecutor" );
191+ Assert .assertNotNull ("configure() did not create _apiJobExecutor - has DAO wiring been moved before pool sizing?" , executor );
192+ return ((ThreadPoolExecutor ) executor ).getCorePoolSize ();
193+ }
194+
195+ private int workerPoolCoreSize () throws Exception {
196+ Object executor = readField ("_workerJobExecutor" );
197+ Assert .assertNotNull ("configure() did not create _workerJobExecutor - has DAO wiring been moved before pool sizing?" , executor );
198+ return ((ThreadPoolExecutor ) executor ).getCorePoolSize ();
199+ }
200+
201+ private Object readField (String name ) throws Exception {
202+ Field f = AsyncJobManagerImpl .class .getDeclaredField (name );
203+ f .setAccessible (true );
204+ return f .get (asyncJobManager );
133205 }
134206
135- private void overrideConfigValue (final ConfigKey configKey , final Object value ) {
207+ private void shutdownIfPresent (String executorFieldName ) {
208+ try {
209+ Object executor = readField (executorFieldName );
210+ if (executor instanceof ExecutorService ) {
211+ ((ExecutorService ) executor ).shutdownNow ();
212+ }
213+ } catch (Exception ignored ) {
214+ // never populated by this test - nothing to clean up
215+ }
216+ }
217+
218+ private void overrideConfigValue (final ConfigKey <?> configKey , final Object value ) {
136219 try {
137220 Field f = ConfigKey .class .getDeclaredField ("_value" );
138221 f .setAccessible (true );
@@ -141,4 +224,19 @@ private void overrideConfigValue(final ConfigKey configKey, final Object value)
141224 Assert .fail (e .getMessage ());
142225 }
143226 }
227+
228+ private void resetConfigValue (final ConfigKey <?> configKey ) throws Exception {
229+ Field f = ConfigKey .class .getDeclaredField ("_value" );
230+ f .setAccessible (true );
231+ f .set (configKey , null );
232+ }
233+
234+ private void setDbPropertiesStatics (Properties props , boolean loaded ) throws Exception {
235+ Field propsField = DbProperties .class .getDeclaredField ("properties" );
236+ Field loadedField = DbProperties .class .getDeclaredField ("loaded" );
237+ propsField .setAccessible (true );
238+ loadedField .setAccessible (true );
239+ propsField .set (null , props );
240+ loadedField .set (null , loaded );
241+ }
144242}
0 commit comments