@@ -24,9 +24,10 @@ use std::time::Instant;
2424
2525use terraphim_firecracker:: { PoolConfig , Sub2SecondOptimizer , VmPoolManager } ;
2626
27+ use super :: ssh:: SshExecutor ;
2728use super :: { Capability , ExecutionContext , ExecutionResult , SnapshotId , ValidationResult } ;
2829use crate :: config:: { BackendType , RlmConfig } ;
29- use crate :: error:: RlmError ;
30+ use crate :: error:: { RlmError , RlmResult } ;
3031use crate :: types:: SessionId ;
3132
3233/// Firecracker execution backend.
@@ -40,11 +41,17 @@ pub struct FirecrackerExecutor {
4041 /// VM pool manager (will be initialized on first use).
4142 pool_manager : Option < Arc < VmPoolManager > > ,
4243
44+ /// SSH executor for running commands on VMs.
45+ ssh_executor : SshExecutor ,
46+
4347 /// Capabilities supported by this executor.
4448 capabilities : Vec < Capability > ,
4549
4650 /// Active snapshots keyed by session.
4751 snapshots : parking_lot:: RwLock < HashMap < SessionId , Vec < SnapshotId > > > ,
52+
53+ /// Session to VM IP mapping for affinity.
54+ session_vms : parking_lot:: RwLock < HashMap < SessionId , String > > ,
4855}
4956
5057impl FirecrackerExecutor {
@@ -78,11 +85,18 @@ impl FirecrackerExecutor {
7885 Capability :: FileOperations ,
7986 ] ;
8087
88+ // Configure SSH executor with sensible defaults for VM access
89+ let ssh_executor = SshExecutor :: new ( )
90+ . with_user ( "root" )
91+ . with_output_dir ( std:: env:: temp_dir ( ) . join ( "terraphim_rlm_output" ) ) ;
92+
8193 Ok ( Self {
8294 config,
8395 pool_manager : None ,
96+ ssh_executor,
8497 capabilities,
8598 snapshots : parking_lot:: RwLock :: new ( HashMap :: new ( ) ) ,
99+ session_vms : parking_lot:: RwLock :: new ( HashMap :: new ( ) ) ,
86100 } )
87101 }
88102
@@ -102,7 +116,7 @@ impl FirecrackerExecutor {
102116 ) ;
103117
104118 // Create pool configuration from RLM config
105- let pool_config = PoolConfig {
119+ let _pool_config = PoolConfig {
106120 min_pool_size : self . config . pool_min_size ,
107121 max_pool_size : self . config . pool_max_size ,
108122 target_pool_size : self . config . pool_target_size ,
@@ -112,7 +126,7 @@ impl FirecrackerExecutor {
112126
113127 // Create optimizer and VM manager
114128 // Note: This is a stub - actual implementation will create real VmManager
115- let optimizer = Arc :: new ( Sub2SecondOptimizer :: new ( ) ) ;
129+ let _optimizer = Arc :: new ( Sub2SecondOptimizer :: new ( ) ) ;
116130
117131 // TODO: Create actual VmManager and VmPoolManager
118132 // For now, we'll return an error indicating initialization is incomplete
@@ -124,6 +138,53 @@ impl FirecrackerExecutor {
124138 } )
125139 }
126140
141+ /// Get or allocate a VM for a session.
142+ ///
143+ /// Returns the VM IP address if available, or None if no VM could be allocated.
144+ async fn get_or_allocate_vm ( & self , session_id : & SessionId ) -> RlmResult < Option < String > > {
145+ // Check if session already has an assigned VM
146+ {
147+ let session_vms = self . session_vms . read ( ) ;
148+ if let Some ( ip) = session_vms. get ( session_id) {
149+ log:: debug!( "Using existing VM for session {}: {}" , session_id, ip) ;
150+ return Ok ( Some ( ip. clone ( ) ) ) ;
151+ }
152+ }
153+
154+ // Try to allocate from pool
155+ // Note: Full pool integration requires terraphim_firecracker enhancements (GitHub #15)
156+ // For now, we check if pool_manager is initialized
157+ if self . pool_manager . is_some ( ) {
158+ // Pool allocation would happen here
159+ // let (vm, _alloc_time) = self.pool_manager.as_ref().unwrap()
160+ // .allocate_vm("terraphim-minimal")
161+ // .await
162+ // .map_err(|e| RlmError::VmAllocationTimeout {
163+ // timeout_ms: self.config.allocation_timeout_ms,
164+ // })?;
165+ //
166+ // if let Some(ip) = vm.read().await.ip_address.clone() {
167+ // self.session_vms.write().insert(*session_id, ip.clone());
168+ // return Ok(Some(ip));
169+ // }
170+ log:: debug!( "VM pool available but allocation not yet implemented" ) ;
171+ }
172+
173+ log:: debug!( "No VM available for session {}" , session_id) ;
174+ Ok ( None )
175+ }
176+
177+ /// Assign a VM to a session (for external allocation).
178+ pub fn assign_vm_to_session ( & self , session_id : SessionId , vm_ip : String ) {
179+ log:: info!( "Assigning VM {} to session {}" , vm_ip, session_id) ;
180+ self . session_vms . write ( ) . insert ( session_id, vm_ip) ;
181+ }
182+
183+ /// Release VM assignment for a session.
184+ pub fn release_session_vm ( & self , session_id : & SessionId ) -> Option < String > {
185+ self . session_vms . write ( ) . remove ( session_id)
186+ }
187+
127188 /// Execute code in a VM.
128189 async fn execute_in_vm (
129190 & self ,
@@ -133,39 +194,78 @@ impl FirecrackerExecutor {
133194 ) -> Result < ExecutionResult , RlmError > {
134195 let start = Instant :: now ( ) ;
135196
136- // For now, return a stub result indicating the executor is not fully implemented
137197 log:: debug!(
138198 "FirecrackerExecutor::execute_in_vm called (python={}, session={})" ,
139199 is_python,
140200 ctx. session_id
141201 ) ;
142202
143- // TODO: Implement actual VM execution:
144- // 1. Allocate VM from pool (or use session-affinity VM)
145- // 2. Copy code to VM via vsock or SSH
146- // 3. Execute and capture output
147- // 4. Handle timeout and cancellation
148- // 5. Return result
203+ // Try to get a VM for this session
204+ let vm_ip = self . get_or_allocate_vm ( & ctx. session_id ) . await ?;
149205
150- let execution_time = start. elapsed ( ) . as_millis ( ) as u64 ;
206+ match vm_ip {
207+ Some ( ref ip) => {
208+ // Execute via SSH on the allocated VM
209+ log:: info!( "Executing on VM {} for session {}" , ip, ctx. session_id) ;
151210
152- Ok ( ExecutionResult {
153- stdout : format ! (
154- "[FirecrackerExecutor stub] Would execute: {}" ,
155- if code. len( ) > 100 {
156- format!( "{}..." , & code[ ..100 ] )
211+ let result = if is_python {
212+ self . ssh_executor . execute_python ( ip, code, ctx) . await
157213 } else {
158- code. to_string( )
214+ self . ssh_executor . execute_command ( ip, code, ctx) . await
215+ } ;
216+
217+ match result {
218+ Ok ( mut res) => {
219+ // Add VM metadata
220+ res. metadata
221+ . insert ( "vm_ip" . to_string ( ) , ip. clone ( ) ) ;
222+ res. metadata
223+ . insert ( "backend" . to_string ( ) , "firecracker" . to_string ( ) ) ;
224+ Ok ( res)
225+ }
226+ Err ( e) => {
227+ log:: error!( "VM execution failed: {}" , e) ;
228+ Err ( e)
229+ }
159230 }
160- ) ,
161- stderr : String :: new ( ) ,
162- exit_code : 0 ,
163- execution_time_ms : execution_time,
164- output_truncated : false ,
165- output_file_path : None ,
166- timed_out : false ,
167- metadata : HashMap :: new ( ) ,
168- } )
231+ }
232+ None => {
233+ // No VM available - return stub response indicating this
234+ // In production, this would be an error, but for development
235+ // we return a stub to allow testing without VMs
236+ log:: warn!(
237+ "No VM available for execution (session={}), returning stub response" ,
238+ ctx. session_id
239+ ) ;
240+
241+ let execution_time = start. elapsed ( ) . as_millis ( ) as u64 ;
242+
243+ Ok ( ExecutionResult {
244+ stdout : format ! (
245+ "[FirecrackerExecutor] No VM available. Would execute: {}" ,
246+ if code. len( ) > 100 {
247+ format!( "{}..." , & code[ ..100 ] )
248+ } else {
249+ code. to_string( )
250+ }
251+ ) ,
252+ stderr : "Warning: No VM allocated for this session. \
253+ Assign a VM using assign_vm_to_session() or ensure VM pool is initialized."
254+ . to_string ( ) ,
255+ exit_code : 0 ,
256+ execution_time_ms : execution_time,
257+ output_truncated : false ,
258+ output_file_path : None ,
259+ timed_out : false ,
260+ metadata : {
261+ let mut m = HashMap :: new ( ) ;
262+ m. insert ( "stub" . to_string ( ) , "true" . to_string ( ) ) ;
263+ m. insert ( "backend" . to_string ( ) , "firecracker" . to_string ( ) ) ;
264+ m
265+ } ,
266+ } )
267+ }
268+ }
169269 }
170270}
171271
0 commit comments