Skip to content

Commit 4f02748

Browse files
committed
Support both vm and container oci images
Signed-off-by: Guvenc Gulce <[email protected]>
1 parent 1687080 commit 4f02748

File tree

8 files changed

+52
-45
lines changed

8 files changed

+52
-45
lines changed

feos/services/image-service/src/filestore.rs

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
22
// SPDX-License-Identifier: Apache-2.0
33

4-
use crate::{FileCommand, PulledImageData, IMAGE_DIR};
5-
use feos_proto::image_service::{ImageInfo, ImageState};
4+
use crate::{FileCommand, ImageInfo, PulledImageData, IMAGE_DIR};
5+
use feos_proto::image_service::ImageState;
66
use flate2::read::GzDecoder;
77
use log::{error, info, warn};
8+
use oci_distribution::manifest;
89
use serde::{Deserialize, Serialize};
910
use std::collections::HashMap;
1011
use std::io::Cursor;
1112
use std::path::Path;
1213
use tar::Archive;
1314
use tokio::{fs, sync::mpsc};
1415

16+
const INITRAMFS_MEDIA_TYPE: &str = "application/vnd.ironcore.image.initramfs.v1alpha1.initramfs";
17+
const VMLINUZ_MEDIA_TYPE: &str = "application/vnd.ironcore.image.vmlinuz.v1alpha1.vmlinuz";
18+
const ROOTFS_MEDIA_TYPE: &str = "application/vnd.ironcore.image.rootfs.v1alpha1.rootfs";
19+
1520
#[derive(Serialize, Deserialize)]
1621
struct ImageMetadata {
1722
image_ref: String,
@@ -86,27 +91,42 @@ impl FileStore {
8691
) -> Result<(), std::io::Error> {
8792
fs::create_dir_all(final_dir).await?;
8893

89-
// Handle both single-blob rootfs (for VMs) and layered rootfs (for containers)
90-
if image_data.layers.len() == 1 && image_ref.contains("cloud-hypervisor") {
91-
// Assuming this is a single rootfs blob for a VM
92-
let final_disk_path = final_dir.join("disk.image");
93-
fs::write(final_disk_path, &image_data.layers[0]).await?;
94-
} else {
95-
// Unpack layers for a container
96-
let rootfs_path = final_dir.join("rootfs");
97-
fs::create_dir_all(&rootfs_path).await?;
98-
for layer_data in image_data.layers {
99-
let cursor = Cursor::new(layer_data);
100-
let decoder = GzDecoder::new(cursor);
101-
let mut archive = Archive::new(decoder);
102-
archive.unpack(&rootfs_path)?;
94+
for layer in image_data.layers {
95+
match layer.media_type.as_str() {
96+
manifest::IMAGE_LAYER_GZIP_MEDIA_TYPE
97+
| manifest::IMAGE_DOCKER_LAYER_GZIP_MEDIA_TYPE => {
98+
let rootfs_path = final_dir.join("rootfs");
99+
if !rootfs_path.exists() {
100+
fs::create_dir_all(&rootfs_path).await?;
101+
}
102+
let cursor = Cursor::new(layer.data);
103+
let decoder = GzDecoder::new(cursor);
104+
let mut archive = Archive::new(decoder);
105+
tokio::task::block_in_place(move || archive.unpack(&rootfs_path))?;
106+
}
107+
ROOTFS_MEDIA_TYPE => {
108+
let path = final_dir.join("disk.image");
109+
fs::write(path, layer.data).await?;
110+
}
111+
INITRAMFS_MEDIA_TYPE => {
112+
let path = final_dir.join("initramfs");
113+
fs::write(path, layer.data).await?;
114+
}
115+
VMLINUZ_MEDIA_TYPE => {
116+
let path = final_dir.join("vmlinuz");
117+
fs::write(path, layer.data).await?;
118+
}
119+
_ => {
120+
warn!(
121+
"FileStore: Skipping layer with unsupported media type: {}",
122+
layer.media_type
123+
);
124+
}
103125
}
104126
}
105127

106-
// Save OCI config.json
107128
fs::write(final_dir.join("config.json"), image_data.config).await?;
108129

109-
// Save internal metadata
110130
let metadata = ImageMetadata {
111131
image_ref: image_ref.to_string(),
112132
};

feos/services/image-service/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,16 @@ pub enum Command {
4646
),
4747
}
4848

49+
#[derive(Debug)]
50+
pub struct PulledLayer {
51+
pub media_type: String,
52+
pub data: Vec<u8>,
53+
}
54+
4955
#[derive(Debug)]
5056
pub struct PulledImageData {
5157
pub config: Vec<u8>,
52-
pub layers: Vec<Vec<u8>>,
58+
pub layers: Vec<PulledLayer>,
5359
}
5460

5561
#[derive(Debug)]

feos/services/image-service/src/worker.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
use crate::{
55
error::ImageServiceError, FileCommand, ImageStateEvent, OrchestratorCommand, PulledImageData,
6+
PulledLayer,
67
};
78
use feos_proto::image_service::{
89
DeleteImageResponse, ImageInfo, ImageState, ImageStatusResponse, ListImagesResponse,
@@ -238,12 +239,10 @@ async fn pull_oci_data(image_ref: &str) -> Result<PulledImageData, ImageServiceE
238239
let reference = Reference::try_from(image_ref.to_string())?;
239240

240241
let accepted_media_types = [
241-
// VM-specific types
242242
ROOTFS_MEDIA_TYPE,
243243
SQUASHFS_MEDIA_TYPE,
244244
INITRAMFS_MEDIA_TYPE,
245245
VMLINUZ_MEDIA_TYPE,
246-
// Standard container layer types
247246
manifest::IMAGE_LAYER_GZIP_MEDIA_TYPE,
248247
manifest::IMAGE_DOCKER_LAYER_GZIP_MEDIA_TYPE,
249248
];
@@ -286,7 +285,10 @@ async fn pull_oci_data(image_ref: &str) -> Result<PulledImageData, ImageServiceE
286285
.pull_blob(&reference, &layer, &mut layer_data)
287286
.await?;
288287
info!("ImagePuller: pulled layer blob {} bytes", layer_data.len());
289-
layers.push(layer_data);
288+
layers.push(PulledLayer {
289+
media_type: layer.media_type.clone(),
290+
data: layer_data,
291+
});
290292
}
291293

292294
if layers.is_empty() {

feos/services/task-service/src/lib.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@ pub use feos_proto::task_service::{
1313

1414
pub const TASK_SERVICE_SOCKET: &str = "/tmp/feos/task_service.sock";
1515

16-
/// Represents the state of a single container managed by the shim.
1716
#[derive(Debug)]
1817
pub struct Container {
1918
pub status: Status,
2019
pub pid: Option<i32>,
2120
pub bundle_path: String,
2221
pub exit_code: Option<i32>,
23-
// Holds the responder for a pending `Wait` call.
2422
pub wait_responder: Option<oneshot::Sender<Result<WaitResponse, TaskError>>>,
2523
}
2624

27-
/// Represents the lifecycle status of a container.
2825
#[derive(Debug, PartialEq, Clone, Copy)]
2926
pub enum Status {
3027
Creating,
@@ -33,7 +30,6 @@ pub enum Status {
3330
Stopped,
3431
}
3532

36-
/// Defines the commands that can be sent from the API layer to the Dispatcher.
3733
#[derive(Debug)]
3834
pub enum Command {
3935
Create {
@@ -58,7 +54,6 @@ pub enum Command {
5854
},
5955
}
6056

61-
/// Internal events sent from workers back to the dispatcher to trigger state changes.
6257
#[derive(Debug)]
6358
pub enum Event {
6459
ContainerCreated { id: String, pid: i32 },

feos/services/task-service/src/worker.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ use tokio::sync::{mpsc, oneshot};
1313

1414
const YOUKI_BIN: &str = "youki";
1515

16-
/// Executes a short-lived youki command (like start, kill, delete) and waits for it to complete.
17-
/// This version is safe as these commands are guaranteed to be short-lived.
1816
async fn run_youki_command(args: &[&str]) -> Result<(), TaskError> {
1917
info!(
2018
"Worker: Executing short-lived command: {} {}",
@@ -45,8 +43,6 @@ async fn run_youki_command(args: &[&str]) -> Result<(), TaskError> {
4543
Ok(())
4644
}
4745

48-
/// Creates a container using the spawn method to avoid hangs, waits for the launcher to exit,
49-
/// and then gets the true container PID from the pid-file.
5046
pub async fn handle_create(
5147
req: CreateRequest,
5248
event_tx: mpsc::Sender<Event>,
@@ -70,10 +66,8 @@ pub async fn handle_create(
7066
args.join(" ")
7167
);
7268

73-
// Use spawn() to avoid potential I/O deadlocks that hang the command.
7469
let child_result = Command::new(YOUKI_BIN)
7570
.args(args)
76-
// Redirect stdio to null to ensure the youki process does not block on I/O.
7771
.stdout(Stdio::null())
7872
.stderr(Stdio::null())
7973
.spawn();
@@ -93,8 +87,6 @@ pub async fn handle_create(
9387
}
9488
};
9589

96-
// Now, wait for the short-lived `youki create` process to exit.
97-
// This is non-blocking and safe because we are not tied to its I/O pipes.
9890
let status = match child.wait().await {
9991
Ok(status) => status,
10092
Err(e) => {
@@ -112,7 +104,6 @@ pub async fn handle_create(
112104
};
113105

114106
if !status.success() {
115-
// Since we redirected output, we can't show it here, but we can report the failure.
116107
let err = TaskError::YoukiCommand(format!(
117108
"youki create exited with non-zero status: {status}"
118109
));
@@ -126,7 +117,6 @@ pub async fn handle_create(
126117
return;
127118
}
128119

129-
// Now that `youki create` has exited, read the PID of the real container process.
130120
let result: Result<i32, TaskError> = async {
131121
let pid_str = tokio::fs::read_to_string(&pid_file)
132122
.await
@@ -160,7 +150,6 @@ pub async fn handle_create(
160150
}
161151
}
162152

163-
/// Starts a previously created container.
164153
pub async fn handle_start(
165154
req: StartRequest,
166155
pid: i32,
@@ -190,7 +179,6 @@ pub async fn handle_start(
190179
}
191180
}
192181

193-
/// Sends a signal to the container's init process.
194182
pub async fn handle_kill(
195183
req: KillRequest,
196184
responder: oneshot::Sender<Result<KillResponse, TaskError>>,
@@ -200,7 +188,6 @@ pub async fn handle_kill(
200188
let _ = responder.send(result.map(|_| KillResponse {}));
201189
}
202190

203-
/// Deletes the container's resources.
204191
pub async fn handle_delete(
205192
req: DeleteRequest,
206193
event_tx: mpsc::Sender<Event>,
@@ -218,7 +205,6 @@ pub async fn handle_delete(
218205
let _ = responder.send(Ok(DeleteResponse {}));
219206
}
220207

221-
/// Waits in the background for a container process to exit and sends an event.
222208
pub async fn wait_for_process_exit(id: String, pid: i32, event_tx: mpsc::Sender<Event>) {
223209
info!("Worker: Background task started, waiting for PID {pid} ({id}) to exit");
224210
let pid_obj = Pid::from_raw(pid);

feos/services/vm-service/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub mod worker;
2424
pub const DEFAULT_VM_DB_URL: &str = "sqlite:/var/lib/feos/vms.db";
2525
pub const VM_API_SOCKET_DIR: &str = "/tmp/feos/vm_api_sockets";
2626
pub const VM_CH_BIN: &str = "cloud-hypervisor";
27-
pub const IMAGE_DIR: &str = "/tmp/feos/images";
27+
pub const IMAGE_DIR: &str = "/var/lib/feos/images";
2828
pub const VM_CONSOLE_DIR: &str = "/tmp/feos/consoles";
2929

3030
#[derive(Debug, Clone)]

feos/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,10 @@ pub async fn run_server(restarted_after_upgrade: bool) -> Result<()> {
4646
info!("Main: Skipping one-time initialization on restart after upgrade.");
4747
}
4848

49-
// This now only sets up the database for the VM service.
5049
let vm_db_url = setup_database().await?;
5150

5251
let (restart_tx, mut restart_rx) = mpsc::channel::<RestartSignal>(1);
5352

54-
// Initialize each service. container_service will now set up its own database.
5553
let vm_service = initialize_vm_service(&vm_db_url).await?;
5654
let container_service = initialize_container_service().await?;
5755

feos/tests/integration/image_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ async fn test_container_image_lifecycle() -> Result<()> {
9494
ensure_server().await;
9595
let mut image_client = get_image_service_client().await?;
9696

97-
let image_ref = "ghcr.io:5000/ironcore-dev/dpservice".to_string();
97+
let image_ref = "ghcr.io:5000/appvia/hello-world/hello-world".to_string();
9898
info!("Pulling container image: {}", image_ref);
9999
let pull_req = PullImageRequest {
100100
image_ref: image_ref.clone(),

0 commit comments

Comments
 (0)