@@ -150,3 +150,171 @@ fn test_free_vram_saturates() {
150150 } ;
151151 assert_eq ! ( dev. free_vram_bytes( ) , 0 ) ;
152152}
153+
154+ #[ test]
155+ fn test_guest_load_reject_strategy ( ) {
156+ let orch = ResourceOrchestrator :: new ( DeploymentModel :: LocalMulti , two_gpu_devices ( ) ) ;
157+ orch. register_tenant (
158+ "guest-a" ,
159+ TenantQuota {
160+ max_guest_load : Some ( GuestLoadPolicy {
161+ max_concurrent_gpu : 1 ,
162+ yield_strategy : YieldStrategy :: Reject ,
163+ } ) ,
164+ ..Default :: default ( )
165+ } ,
166+ )
167+ . unwrap ( ) ;
168+
169+ let req = test_request ( "guest-a" , 3 ) ;
170+ let _alloc1 = orch. allocate ( & req) . unwrap ( ) ;
171+ let result = orch. allocate ( & req) ;
172+ assert ! ( result. is_err( ) ) ;
173+ let err = result. unwrap_err ( ) . to_string ( ) ;
174+ assert ! ( err. contains( "strategy: reject" ) , "got: {err}" ) ;
175+ }
176+
177+ #[ test]
178+ fn test_guest_load_queue_strategy ( ) {
179+ let orch = ResourceOrchestrator :: new ( DeploymentModel :: LocalMulti , two_gpu_devices ( ) ) ;
180+ orch. register_tenant (
181+ "guest-b" ,
182+ TenantQuota {
183+ max_guest_load : Some ( GuestLoadPolicy {
184+ max_concurrent_gpu : 1 ,
185+ yield_strategy : YieldStrategy :: Queue ,
186+ } ) ,
187+ ..Default :: default ( )
188+ } ,
189+ )
190+ . unwrap ( ) ;
191+
192+ let req = test_request ( "guest-b" , 3 ) ;
193+ let _alloc1 = orch. allocate ( & req) . unwrap ( ) ;
194+ let result = orch. allocate ( & req) ;
195+ assert ! ( result. is_err( ) ) ;
196+ let err = result. unwrap_err ( ) . to_string ( ) ;
197+ assert ! ( err. contains( "strategy: queue" ) , "got: {err}" ) ;
198+ }
199+
200+ #[ test]
201+ fn test_guest_load_defer_power_cycle_strategy ( ) {
202+ let orch = ResourceOrchestrator :: new ( DeploymentModel :: LocalMulti , two_gpu_devices ( ) ) ;
203+ orch. register_tenant (
204+ "guest-c" ,
205+ TenantQuota {
206+ max_guest_load : Some ( GuestLoadPolicy {
207+ max_concurrent_gpu : 1 ,
208+ yield_strategy : YieldStrategy :: DeferUntilPowerCycle ,
209+ } ) ,
210+ ..Default :: default ( )
211+ } ,
212+ )
213+ . unwrap ( ) ;
214+
215+ let req = test_request ( "guest-c" , 3 ) ;
216+ let _alloc1 = orch. allocate ( & req) . unwrap ( ) ;
217+ let result = orch. allocate ( & req) ;
218+ assert ! ( result. is_err( ) ) ;
219+ let err = result. unwrap_err ( ) . to_string ( ) ;
220+ assert ! (
221+ err. contains( "strategy: defer_until_power_cycle" ) ,
222+ "got: {err}"
223+ ) ;
224+ }
225+
226+ #[ test]
227+ fn test_guest_load_under_threshold_passes ( ) {
228+ let orch = ResourceOrchestrator :: new ( DeploymentModel :: LocalMulti , two_gpu_devices ( ) ) ;
229+ orch. register_tenant (
230+ "guest-d" ,
231+ TenantQuota {
232+ max_guest_load : Some ( GuestLoadPolicy {
233+ max_concurrent_gpu : 2 ,
234+ yield_strategy : YieldStrategy :: Reject ,
235+ } ) ,
236+ ..Default :: default ( )
237+ } ,
238+ )
239+ . unwrap ( ) ;
240+
241+ let req = test_request ( "guest-d" , 3 ) ;
242+ let alloc1 = orch. allocate ( & req) . unwrap ( ) ;
243+ assert ! ( !alloc1. exclusive) ;
244+ }
245+
246+ #[ test]
247+ fn test_guest_load_none_means_unlimited ( ) {
248+ let orch = ResourceOrchestrator :: new ( DeploymentModel :: LocalMulti , two_gpu_devices ( ) ) ;
249+ orch. register_tenant (
250+ "guest-e" ,
251+ TenantQuota {
252+ max_guest_load : None ,
253+ ..Default :: default ( )
254+ } ,
255+ )
256+ . unwrap ( ) ;
257+
258+ let req = test_request ( "guest-e" , 3 ) ;
259+ let _alloc1 = orch. allocate ( & req) . unwrap ( ) ;
260+ let alloc2 = orch. allocate ( & req) ;
261+ assert ! ( alloc2. is_ok( ) ) ;
262+ }
263+
264+ #[ test]
265+ fn test_guest_load_release_allows_reallocation ( ) {
266+ let orch = ResourceOrchestrator :: new ( DeploymentModel :: LocalMulti , two_gpu_devices ( ) ) ;
267+ orch. register_tenant (
268+ "guest-f" ,
269+ TenantQuota {
270+ max_guest_load : Some ( GuestLoadPolicy {
271+ max_concurrent_gpu : 1 ,
272+ yield_strategy : YieldStrategy :: Reject ,
273+ } ) ,
274+ ..Default :: default ( )
275+ } ,
276+ )
277+ . unwrap ( ) ;
278+
279+ let req = test_request ( "guest-f" , 3 ) ;
280+ let alloc1 = orch. allocate ( & req) . unwrap ( ) ;
281+ let result = orch. allocate ( & req) ;
282+ assert ! ( result. is_err( ) ) ;
283+
284+ orch. release ( "guest-f" , alloc1. device_index ) . unwrap ( ) ;
285+ let alloc2 = orch. allocate ( & req) ;
286+ assert ! ( alloc2. is_ok( ) ) ;
287+ }
288+
289+ #[ test]
290+ fn test_guest_load_default_strategy_is_queue ( ) {
291+ assert_eq ! ( YieldStrategy :: default ( ) , YieldStrategy :: Queue ) ;
292+ }
293+
294+ #[ test]
295+ fn test_guest_load_policy_serde_roundtrip ( ) {
296+ let policy = GuestLoadPolicy {
297+ max_concurrent_gpu : 4 ,
298+ yield_strategy : YieldStrategy :: DeferUntilPowerCycle ,
299+ } ;
300+ let json = serde_json:: to_string ( & policy) . unwrap ( ) ;
301+ let parsed: GuestLoadPolicy = serde_json:: from_str ( & json) . unwrap ( ) ;
302+ assert_eq ! ( parsed. max_concurrent_gpu, 4 ) ;
303+ assert_eq ! ( parsed. yield_strategy, YieldStrategy :: DeferUntilPowerCycle ) ;
304+ }
305+
306+ #[ test]
307+ fn test_yield_strategy_serde_names ( ) {
308+ assert_eq ! (
309+ serde_json:: to_string( & YieldStrategy :: Queue ) . unwrap( ) ,
310+ "\" queue\" "
311+ ) ;
312+ assert_eq ! (
313+ serde_json:: to_string( & YieldStrategy :: Reject ) . unwrap( ) ,
314+ "\" reject\" "
315+ ) ;
316+ assert_eq ! (
317+ serde_json:: to_string( & YieldStrategy :: DeferUntilPowerCycle ) . unwrap( ) ,
318+ "\" defer_until_power_cycle\" "
319+ ) ;
320+ }
0 commit comments