Skip to content

Commit 0a21c80

Browse files
committed
Combine create and start VM as a new command in the cli
Signed-off-by: Guvenc Gulce <[email protected]>
1 parent 2eafe06 commit 0a21c80

File tree

3 files changed

+185
-2
lines changed

3 files changed

+185
-2
lines changed

cli/src/vm_commands.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
177360
async fn create_vm(
178361
client: &mut VmServiceClient<Channel>,
179362
image_ref: String,

hack/initramfs/mk-initramfs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ EOF
2828
echo "feos" > etc/hostname
2929

3030
cat <<EOF >>etc/resolv.conf
31-
nameserver 2a10:afc0:e01f:1::ffff:0
31+
nameserver 2001:4860:4860::6464
3232
EOF
3333

3434
echo "Install libraries for FeOS (copy from host)"

hack/kernel/cmdline.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
console=tty0 console=ttyS0,115200
1+
console=tty0

0 commit comments

Comments
 (0)