@@ -97,6 +97,33 @@ pub enum VmCommand {
9797 #[ arg( required = true , help = "VM identifier" ) ]
9898 vm_id : String ,
9999 } ,
100+ /// Create and start a virtual machine in one operation
101+ CreateAndStart {
102+ #[ arg(
103+ long,
104+ required = true ,
105+ help = "Container image reference to use for the VM"
106+ ) ]
107+ image_ref : String ,
108+
109+ #[ arg( long, default_value_t = 1 , help = "Number of virtual CPUs to allocate" ) ]
110+ vcpus : u32 ,
111+
112+ #[ arg( long, default_value_t = 1024 , help = "Memory size in MiB" ) ]
113+ memory : u64 ,
114+
115+ #[ arg( long, help = "Optional custom VM identifier" ) ]
116+ vm_id : Option < String > ,
117+
118+ #[ arg(
119+ long,
120+ help = "PCI device BDF to passthrough for networking (e.g., 0000:03:00.0)"
121+ ) ]
122+ pci_device : Vec < String > ,
123+
124+ #[ arg( long, help = "Enable hugepages for memory allocation" ) ]
125+ hugepages : bool ,
126+ } ,
100127 /// Watch virtual machine state change events
101128 Events {
102129 #[ arg(
@@ -163,6 +190,25 @@ pub async fn handle_vm_command(args: VmArgs) -> Result<()> {
163190 VmCommand :: Pause { vm_id } => pause_vm ( & mut client, vm_id) . await ?,
164191 VmCommand :: Resume { vm_id } => resume_vm ( & mut client, vm_id) . await ?,
165192 VmCommand :: Delete { vm_id } => delete_vm ( & mut client, vm_id) . await ?,
193+ VmCommand :: CreateAndStart {
194+ image_ref,
195+ vcpus,
196+ memory,
197+ vm_id,
198+ pci_device,
199+ hugepages,
200+ } => {
201+ create_and_start_vm (
202+ & mut client,
203+ image_ref,
204+ vcpus,
205+ memory,
206+ vm_id,
207+ pci_device,
208+ hugepages,
209+ )
210+ . await ?
211+ }
166212 VmCommand :: Events { vm_id } => watch_events ( & mut client, vm_id) . await ?,
167213 VmCommand :: Console { vm_id } => console_vm ( & mut client, vm_id) . await ?,
168214 VmCommand :: AttachDisk { vm_id, path } => attach_disk ( & mut client, vm_id, path) . await ?,
@@ -174,6 +220,143 @@ pub async fn handle_vm_command(args: VmArgs) -> Result<()> {
174220 Ok ( ( ) )
175221}
176222
223+ async fn create_and_start_vm (
224+ client : & mut VmServiceClient < Channel > ,
225+ image_ref : String ,
226+ vcpus : u32 ,
227+ memory : u64 ,
228+ vm_id : Option < String > ,
229+ pci_devices : Vec < String > ,
230+ hugepages : bool ,
231+ ) -> Result < ( ) > {
232+ println ! ( "🚀 Starting create and start operation for VM with image: {image_ref}" ) ;
233+
234+ // Step 1: Create the VM
235+ println ! ( "📋 Step 1: Creating VM..." ) ;
236+
237+ let net_configs = pci_devices
238+ . iter ( )
239+ . map ( |bdf| {
240+ println ! ( " Adding PCI device: {bdf}" ) ;
241+ NetConfig {
242+ backend : Some ( net_config:: Backend :: VfioPci ( VfioPciConfig {
243+ bdf : bdf. clone ( ) ,
244+ } ) ) ,
245+ ..Default :: default ( )
246+ }
247+ } )
248+ . collect ( ) ;
249+
250+ let request = CreateVmRequest {
251+ config : Some ( VmConfig {
252+ cpus : Some ( CpuConfig {
253+ boot_vcpus : vcpus,
254+ max_vcpus : vcpus,
255+ } ) ,
256+ memory : Some ( MemoryConfig {
257+ size_mib : memory,
258+ hugepages,
259+ } ) ,
260+ image_ref : image_ref. clone ( ) ,
261+ net : net_configs,
262+ ..Default :: default ( )
263+ } ) ,
264+ vm_id : vm_id. clone ( ) ,
265+ } ;
266+
267+ let response = client. create_vm ( request) . await ?. into_inner ( ) ;
268+ let vm_id = response. vm_id ;
269+ println ! ( "✅ VM created successfully with ID: {vm_id}" ) ;
270+
271+ // Step 2: Wait for VM to be in 'Created' state
272+ println ! ( "⏳ Step 2: Waiting for VM to reach 'Created' state..." ) ;
273+ wait_for_vm_state ( client, & vm_id, VmState :: Created ) . await ?;
274+ println ! ( "✅ VM is now in 'Created' state" ) ;
275+
276+ // Step 3: Start the VM
277+ println ! ( "🔄 Step 3: Starting VM..." ) ;
278+ let start_request = StartVmRequest {
279+ vm_id : vm_id. clone ( ) ,
280+ } ;
281+ client. start_vm ( start_request) . await ?;
282+ println ! ( "✅ Start request sent successfully" ) ;
283+
284+ // Step 4: Wait for VM to be in 'Running' state
285+ println ! ( "⏳ Step 4: Waiting for VM to reach 'Running' state..." ) ;
286+ wait_for_vm_state ( client, & vm_id, VmState :: Running ) . await ?;
287+ println ! ( "🎉 VM '{vm_id}' is now running successfully!" ) ;
288+
289+ println ! ( "Use 'feos-cli vm console {vm_id}' to connect to the VM console." ) ;
290+
291+ Ok ( ( ) )
292+ }
293+
294+ async fn wait_for_vm_state (
295+ client : & mut VmServiceClient < Channel > ,
296+ vm_id : & str ,
297+ target_state : VmState ,
298+ ) -> Result < ( ) > {
299+ let request = StreamVmEventsRequest {
300+ vm_id : Some ( vm_id. to_string ( ) ) ,
301+ ..Default :: default ( )
302+ } ;
303+
304+ let mut stream = client. stream_vm_events ( request) . await ?. into_inner ( ) ;
305+
306+ // First, check current state
307+ let get_request = GetVmRequest {
308+ vm_id : vm_id. to_string ( ) ,
309+ } ;
310+ let current_vm = client. get_vm ( get_request) . await ?. into_inner ( ) ;
311+ let current_state = VmState :: try_from ( current_vm. state ) . unwrap_or ( VmState :: Unspecified ) ;
312+
313+ if current_state == target_state {
314+ return Ok ( ( ) ) ;
315+ }
316+
317+ println ! ( " Current state: {current_state:?}, waiting for: {target_state:?}" ) ;
318+
319+ // Listen for state changes
320+ while let Some ( event) = stream. next ( ) . await {
321+ match event {
322+ Ok ( event) => {
323+ if let Some ( data) = event. data {
324+ if data
325+ . type_url
326+ . contains ( "feos.vm.vmm.api.v1.VmStateChangedEvent" )
327+ {
328+ let state_change = VmStateChangedEvent :: decode ( & * data. value ) ?;
329+ let new_state = VmState :: try_from ( state_change. new_state )
330+ . unwrap_or ( VmState :: Unspecified ) ;
331+
332+ println ! (
333+ " State transition: {:?} ({})" ,
334+ new_state, state_change. reason
335+ ) ;
336+
337+ if new_state == target_state {
338+ return Ok ( ( ) ) ;
339+ }
340+
341+ // Check for error states
342+ if new_state == VmState :: Crashed {
343+ anyhow:: bail!( "VM entered crashed state: {}" , state_change. reason) ;
344+ }
345+ }
346+ }
347+ }
348+ Err ( status) => {
349+ anyhow:: bail!( "Error in event stream: {}" , status) ;
350+ }
351+ }
352+ }
353+
354+ anyhow:: bail!(
355+ "Event stream ended before reaching target state: {:?}" ,
356+ target_state
357+ )
358+ }
359+
177360async fn create_vm (
178361 client : & mut VmServiceClient < Channel > ,
179362 image_ref : String ,
0 commit comments