@@ -39,18 +39,18 @@ use openshell_core::proto::{
3939 GetClusterInferenceRequest , GetDraftHistoryRequest , GetDraftPolicyRequest ,
4040 GetGatewayConfigRequest , GetProviderProfileRequest , GetProviderRefreshStatusRequest ,
4141 GetProviderRequest , GetSandboxConfigRequest , GetSandboxLogsRequest ,
42- GetSandboxPolicyStatusRequest , GetSandboxRequest , GetServiceRequest , GpuRequestSpec ,
42+ GetSandboxPolicyStatusRequest , GetSandboxRequest , GetServiceRequest , GpuResourceRequirement ,
4343 HealthRequest , ImportProviderProfilesRequest , LintProviderProfilesRequest ,
4444 ListProviderProfilesRequest , ListProvidersRequest , ListSandboxPoliciesRequest ,
4545 ListSandboxProvidersRequest , ListSandboxesRequest , ListServicesRequest , PlatformEvent ,
4646 PolicySource , PolicyStatus , Provider , ProviderCredentialRefreshStatus ,
4747 ProviderCredentialRefreshStrategy , ProviderProfile , ProviderProfileDiagnostic ,
4848 ProviderProfileImportItem , RejectDraftChunkRequest , RevokeSshSessionRequest ,
49- RotateProviderCredentialRequest , Sandbox , SandboxPhase , SandboxPolicy , SandboxSpec ,
50- SandboxTemplate , ServiceEndpointResponse , SetClusterInferenceRequest , SettingScope ,
51- SettingValue , TcpForwardFrame , TcpForwardInit , TcpRelayTarget , UpdateConfigRequest ,
52- UpdateProviderRequest , WatchSandboxRequest , exec_sandbox_event , setting_value ,
53- tcp_forward_init,
49+ RotateProviderCredentialRequest , Sandbox , SandboxPhase , SandboxPolicy ,
50+ SandboxResourceRequirements , SandboxSpec , SandboxTemplate , ServiceEndpointResponse ,
51+ SetClusterInferenceRequest , SettingScope , SettingValue , TcpForwardFrame , TcpForwardInit ,
52+ TcpRelayTarget , UpdateConfigRequest , UpdateProviderRequest , WatchSandboxRequest ,
53+ exec_sandbox_event , setting_value , tcp_forward_init,
5454} ;
5555use openshell_core:: settings:: { self , SettingValueKind } ;
5656use openshell_core:: { ObjectId , ObjectName } ;
@@ -1801,11 +1801,6 @@ pub async fn sandbox_create(
18011801 }
18021802 None => None ,
18031803 } ;
1804- let requested_gpu = gpu
1805- || gpu_device. is_some_and ( |device_id| !device_id. is_empty ( ) )
1806- || gpu_count. is_some ( )
1807- || image. as_deref ( ) . is_some_and ( image_requests_gpu) ;
1808-
18091804 let providers_v2_enabled = gateway_providers_v2_enabled ( & mut client) . await ?;
18101805 let inferred_types: Vec < String > = if providers_v2_enabled {
18111806 Vec :: new ( )
@@ -1825,6 +1820,11 @@ pub async fn sandbox_create(
18251820 let driver_config = driver_config_json
18261821 . map ( parse_driver_config_json)
18271822 . transpose ( ) ?;
1823+ let resource_requirements =
1824+ resource_requirements_from_cli ( image. as_deref ( ) , gpu, gpu_device, gpu_count) ;
1825+ let requested_gpu = resource_requirements
1826+ . as_ref ( )
1827+ . is_some_and ( |requirements| requirements. gpu . is_some ( ) ) ;
18281828
18291829 let template = if image. is_some ( ) || resource_limits. is_some ( ) || driver_config. is_some ( ) {
18301830 Some ( SandboxTemplate {
@@ -1839,7 +1839,7 @@ pub async fn sandbox_create(
18391839
18401840 let request = CreateSandboxRequest {
18411841 spec : Some ( SandboxSpec {
1842- gpu : gpu_request_from_cli ( requested_gpu , gpu_device , gpu_count ) ,
1842+ resource_requirements ,
18431843 policy,
18441844 providers : configured_providers,
18451845 template,
@@ -2274,17 +2274,26 @@ pub async fn sandbox_create(
22742274 }
22752275}
22762276
2277- fn gpu_request_from_cli (
2278- requested_gpu : bool ,
2277+ fn resource_requirements_from_cli (
2278+ image : Option < & str > ,
2279+ gpu : bool ,
22792280 gpu_device : Option < & str > ,
22802281 gpu_count : Option < u32 > ,
2281- ) -> Option < GpuRequestSpec > {
2282- requested_gpu. then ( || GpuRequestSpec {
2283- device_id : gpu_device
2284- . filter ( |device_id| !device_id. is_empty ( ) )
2285- . map ( |device_id| vec ! [ device_id. to_string( ) ] )
2286- . unwrap_or_default ( ) ,
2287- count : gpu_count,
2282+ ) -> Option < SandboxResourceRequirements > {
2283+ let device_ids = gpu_device
2284+ . filter ( |device_id| !device_id. is_empty ( ) )
2285+ . map ( |device_id| vec ! [ device_id. to_string( ) ] )
2286+ . unwrap_or_default ( ) ;
2287+ let requested_gpu = gpu
2288+ || gpu_count. is_some ( )
2289+ || !device_ids. is_empty ( )
2290+ || image. is_some_and ( image_requests_gpu) ;
2291+
2292+ requested_gpu. then_some ( SandboxResourceRequirements {
2293+ gpu : Some ( GpuResourceRequirement {
2294+ device_ids,
2295+ count : gpu_count,
2296+ } ) ,
22882297 } )
22892298}
22902299
@@ -7537,15 +7546,14 @@ mod tests {
75377546 dockerfile_sources_supported_for_gateway, format_endpoint, format_gateway_select_header,
75387547 format_gateway_select_items, format_provider_attachment_table, gateway_add,
75397548 gateway_auth_label, gateway_env_override_warning, gateway_select_with, gateway_type_label,
7540- git_sync_files, gpu_request_from_cli, http_health_check, image_requests_gpu,
7541- import_local_package_mtls_bundle, inferred_provider_type, mtls_certs_exist_for_gateway,
7542- package_managed_tls_dirs,
7549+ git_sync_files, http_health_check, image_requests_gpu, import_local_package_mtls_bundle,
7550+ inferred_provider_type, mtls_certs_exist_for_gateway, package_managed_tls_dirs,
75437551 parse_cli_setting_value, parse_credential_expiry_cli_value, parse_credential_expiry_pairs,
75447552 parse_credential_pairs, parse_driver_config_json, plaintext_gateway_is_remote,
75457553 progress_step_from_metadata, provider_profile_allows_refresh_bootstrap,
75467554 provisioning_timeout_message, ready_false_condition_message, refresh_status_header,
7547- refresh_status_row, resolve_from, sandbox_should_persist , sandbox_upload_plan ,
7548- service_expose_status_error, service_url_for_gateway,
7555+ refresh_status_row, resolve_from, resource_requirements_from_cli , sandbox_should_persist ,
7556+ sandbox_upload_plan , service_expose_status_error, service_url_for_gateway,
75497557 } ;
75507558 use crate :: TEST_ENV_LOCK ;
75517559 use hyper:: StatusCode ;
@@ -8075,43 +8083,64 @@ mod tests {
80758083 }
80768084
80778085 #[ test]
8078- fn gpu_request_from_cli_uses_presence_with_empty_device_ids_for_default_gpu ( ) {
8079- let request =
8080- gpu_request_from_cli ( true , None , None ) . expect ( "gpu request should be present" ) ;
8086+ fn resource_requirements_from_cli_uses_presence_for_default_gpu ( ) {
8087+ let requirements = resource_requirements_from_cli ( None , true , None , None )
8088+ . expect ( "resource requirements should be present" ) ;
8089+ let gpu = requirements. gpu . expect ( "GPU requirement should be present" ) ;
80818090
8082- assert ! ( request . device_id . is_empty( ) ) ;
8083- assert_eq ! ( request . count, None ) ;
8091+ assert ! ( gpu . device_ids . is_empty( ) ) ;
8092+ assert_eq ! ( gpu . count, None ) ;
80848093 }
80858094
80868095 #[ test]
8087- fn gpu_request_from_cli_maps_gpu_device_to_one_device_id ( ) {
8088- let request = gpu_request_from_cli ( true , Some ( "0000:2d:00.0" ) , None )
8089- . expect ( "gpu request should be present" ) ;
8096+ fn resource_requirements_from_cli_maps_gpu_device_to_one_device_id ( ) {
8097+ let requirements = resource_requirements_from_cli ( None , false , Some ( "0000:2d:00.0" ) , None )
8098+ . expect ( "resource requirements should be present" ) ;
8099+ let gpu = requirements. gpu . expect ( "GPU requirement should be present" ) ;
80908100
8091- assert_eq ! ( request . device_id , vec![ "0000:2d:00.0" ] ) ;
8092- assert_eq ! ( request . count, None ) ;
8101+ assert_eq ! ( gpu . device_ids , vec![ "0000:2d:00.0" ] ) ;
8102+ assert_eq ! ( gpu . count, None ) ;
80938103 }
80948104
80958105 #[ test]
8096- fn gpu_request_from_cli_maps_gpu_count ( ) {
8097- let request = gpu_request_from_cli ( true , None , Some ( 2 ) ) . expect ( "gpu request should exist" ) ;
8106+ fn resource_requirements_from_cli_maps_gpu_count ( ) {
8107+ let requirements = resource_requirements_from_cli ( None , false , None , Some ( 2 ) )
8108+ . expect ( "requirements should exist" ) ;
8109+ let gpu = requirements. gpu . expect ( "GPU requirement should be present" ) ;
80988110
8099- assert ! ( request . device_id . is_empty( ) ) ;
8100- assert_eq ! ( request . count, Some ( 2 ) ) ;
8111+ assert ! ( gpu . device_ids . is_empty( ) ) ;
8112+ assert_eq ! ( gpu . count, Some ( 2 ) ) ;
81018113 }
81028114
81038115 #[ test]
8104- fn gpu_request_from_cli_preserves_device_and_gpu_count_for_gateway_validation ( ) {
8105- let request = gpu_request_from_cli ( true , Some ( "nvidia.com/gpu=0" ) , Some ( 2 ) )
8106- . expect ( "gpu request should exist" ) ;
8116+ fn resource_requirements_from_cli_preserves_device_and_gpu_count_for_gateway_validation ( ) {
8117+ let requirements =
8118+ resource_requirements_from_cli ( None , false , Some ( "nvidia.com/gpu=0" ) , Some ( 2 ) )
8119+ . expect ( "requirements should exist" ) ;
8120+ let gpu = requirements. gpu . expect ( "GPU requirement should be present" ) ;
81078121
8108- assert_eq ! ( request . device_id , vec![ "nvidia.com/gpu=0" ] ) ;
8109- assert_eq ! ( request . count, Some ( 2 ) ) ;
8122+ assert_eq ! ( gpu . device_ids , vec![ "nvidia.com/gpu=0" ] ) ;
8123+ assert_eq ! ( gpu . count, Some ( 2 ) ) ;
81108124 }
81118125
81128126 #[ test]
8113- fn gpu_request_from_cli_omits_gpu_request_when_not_requested ( ) {
8114- assert ! ( gpu_request_from_cli( false , Some ( "0" ) , None ) . is_none( ) ) ;
8127+ fn resource_requirements_from_cli_omits_gpu_request_when_not_requested ( ) {
8128+ assert ! ( resource_requirements_from_cli( None , false , None , None ) . is_none( ) ) ;
8129+ }
8130+
8131+ #[ test]
8132+ fn resource_requirements_from_cli_infers_gpu_from_image ( ) {
8133+ let requirements = resource_requirements_from_cli (
8134+ Some ( "ghcr.io/nvidia/openshell-community/sandboxes/nvidia-gpu:latest" ) ,
8135+ false ,
8136+ None ,
8137+ None ,
8138+ )
8139+ . expect ( "resource requirements should be present" ) ;
8140+ let gpu = requirements. gpu . expect ( "GPU requirement should be present" ) ;
8141+
8142+ assert ! ( gpu. device_ids. is_empty( ) ) ;
8143+ assert_eq ! ( gpu. count, None ) ;
81158144 }
81168145
81178146 #[ test]
0 commit comments