mirror of
https://github.com/edera-dev/krata.git
synced 2025-08-03 13:11:31 +00:00
feat: implement kernel / initrd oci image support (#103)
* feat: implement kernel / initrd oci image support * fix: implement image urls more faithfully
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1451,6 +1451,7 @@ dependencies = [
|
|||||||
"krata",
|
"krata",
|
||||||
"krata-oci",
|
"krata-oci",
|
||||||
"krata-runtime",
|
"krata-runtime",
|
||||||
|
"krata-tokio-tar",
|
||||||
"log",
|
"log",
|
||||||
"prost",
|
"prost",
|
||||||
"redb",
|
"redb",
|
||||||
|
@ -64,6 +64,10 @@ pub struct LauchCommand {
|
|||||||
help = "Wait for the guest to start, implied by --attach"
|
help = "Wait for the guest to start, implied by --attach"
|
||||||
)]
|
)]
|
||||||
wait: bool,
|
wait: bool,
|
||||||
|
#[arg(short = 'k', long, help = "OCI kernel image for guest to use")]
|
||||||
|
kernel: Option<String>,
|
||||||
|
#[arg(short = 'I', long, help = "OCI initrd image for guest to use")]
|
||||||
|
initrd: Option<String>,
|
||||||
#[arg(help = "Container image for guest to use")]
|
#[arg(help = "Container image for guest to use")]
|
||||||
oci: String,
|
oci: String,
|
||||||
#[arg(
|
#[arg(
|
||||||
@ -80,27 +84,41 @@ impl LauchCommand {
|
|||||||
mut client: ControlServiceClient<Channel>,
|
mut client: ControlServiceClient<Channel>,
|
||||||
events: EventStream,
|
events: EventStream,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let response = client
|
let image = self
|
||||||
.pull_image(PullImageRequest {
|
.pull_image(
|
||||||
image: self.oci.clone(),
|
&mut client,
|
||||||
format: match self.image_format {
|
&self.oci,
|
||||||
LaunchImageFormat::Squashfs => OciImageFormat::Squashfs.into(),
|
match self.image_format {
|
||||||
LaunchImageFormat::Erofs => OciImageFormat::Erofs.into(),
|
LaunchImageFormat::Squashfs => OciImageFormat::Squashfs,
|
||||||
|
LaunchImageFormat::Erofs => OciImageFormat::Erofs,
|
||||||
},
|
},
|
||||||
overwrite_cache: self.pull_overwrite_cache,
|
)
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
let reply = pull_interactive_progress(response.into_inner()).await?;
|
|
||||||
|
let kernel = if let Some(ref kernel) = self.kernel {
|
||||||
|
let kernel_image = self
|
||||||
|
.pull_image(&mut client, kernel, OciImageFormat::Tar)
|
||||||
|
.await?;
|
||||||
|
Some(kernel_image)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let initrd = if let Some(ref initrd) = self.initrd {
|
||||||
|
let kernel_image = self
|
||||||
|
.pull_image(&mut client, initrd, OciImageFormat::Tar)
|
||||||
|
.await?;
|
||||||
|
Some(kernel_image)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let request = CreateGuestRequest {
|
let request = CreateGuestRequest {
|
||||||
spec: Some(GuestSpec {
|
spec: Some(GuestSpec {
|
||||||
name: self.name.unwrap_or_default(),
|
name: self.name.unwrap_or_default(),
|
||||||
image: Some(GuestImageSpec {
|
image: Some(image),
|
||||||
image: Some(Image::Oci(GuestOciImageSpec {
|
kernel,
|
||||||
digest: reply.digest,
|
initrd,
|
||||||
format: reply.format,
|
|
||||||
})),
|
|
||||||
}),
|
|
||||||
vcpus: self.cpus,
|
vcpus: self.cpus,
|
||||||
mem: self.mem,
|
mem: self.mem,
|
||||||
task: Some(GuestTaskSpec {
|
task: Some(GuestTaskSpec {
|
||||||
@ -146,6 +164,28 @@ impl LauchCommand {
|
|||||||
StdioConsoleStream::restore_terminal_mode();
|
StdioConsoleStream::restore_terminal_mode();
|
||||||
std::process::exit(code.unwrap_or(0));
|
std::process::exit(code.unwrap_or(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn pull_image(
|
||||||
|
&self,
|
||||||
|
client: &mut ControlServiceClient<Channel>,
|
||||||
|
image: &str,
|
||||||
|
format: OciImageFormat,
|
||||||
|
) -> Result<GuestImageSpec> {
|
||||||
|
let response = client
|
||||||
|
.pull_image(PullImageRequest {
|
||||||
|
image: image.to_string(),
|
||||||
|
format: format.into(),
|
||||||
|
overwrite_cache: self.pull_overwrite_cache,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
let reply = pull_interactive_progress(response.into_inner()).await?;
|
||||||
|
Ok(GuestImageSpec {
|
||||||
|
image: Some(Image::Oci(GuestOciImageSpec {
|
||||||
|
digest: reply.digest,
|
||||||
|
format: reply.format,
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
|
async fn wait_guest_started(id: &str, events: EventStream) -> Result<()> {
|
||||||
|
@ -27,6 +27,7 @@ scopeguard = { workspace = true }
|
|||||||
signal-hook = { workspace = true }
|
signal-hook = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tokio-stream = { workspace = true }
|
tokio-stream = { workspace = true }
|
||||||
|
krata-tokio-tar = { workspace = true }
|
||||||
tonic = { workspace = true, features = ["tls"] }
|
tonic = { workspace = true, features = ["tls"] }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
|
|
||||||
|
@ -378,7 +378,7 @@ impl ControlService for DaemonControlService {
|
|||||||
format: match packed.format {
|
format: match packed.format {
|
||||||
OciPackedFormat::Squashfs => OciImageFormat::Squashfs.into(),
|
OciPackedFormat::Squashfs => OciImageFormat::Squashfs.into(),
|
||||||
OciPackedFormat::Erofs => OciImageFormat::Erofs.into(),
|
OciPackedFormat::Erofs => OciImageFormat::Erofs.into(),
|
||||||
_ => OciImageFormat::Unknown.into(),
|
OciPackedFormat::Tar => OciImageFormat::Tar.into(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
yield reply;
|
yield reply;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::{net::SocketAddr, path::PathBuf, str::FromStr};
|
use std::{net::SocketAddr, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{anyhow, Result};
|
||||||
use console::{DaemonConsole, DaemonConsoleHandle};
|
use console::{DaemonConsole, DaemonConsoleHandle};
|
||||||
use control::DaemonControlService;
|
use control::DaemonControlService;
|
||||||
use db::GuestStore;
|
use db::GuestStore;
|
||||||
@ -74,9 +74,11 @@ impl Daemon {
|
|||||||
generated
|
generated
|
||||||
};
|
};
|
||||||
|
|
||||||
let packer = OciPackerService::new(None, &image_cache_dir, OciPlatform::current()).await?;
|
let initrd_path = detect_guest_file(&store, "initrd")?;
|
||||||
|
let kernel_path = detect_guest_file(&store, "kernel")?;
|
||||||
|
|
||||||
let runtime = Runtime::new(store.clone()).await?;
|
let packer = OciPackerService::new(None, &image_cache_dir, OciPlatform::current()).await?;
|
||||||
|
let runtime = Runtime::new().await?;
|
||||||
let glt = GuestLookupTable::new(0, host_uuid);
|
let glt = GuestLookupTable::new(0, host_uuid);
|
||||||
let guests_db_path = format!("{}/guests.db", store);
|
let guests_db_path = format!("{}/guests.db", store);
|
||||||
let guests = GuestStore::open(&PathBuf::from(guests_db_path))?;
|
let guests = GuestStore::open(&PathBuf::from(guests_db_path))?;
|
||||||
@ -97,6 +99,8 @@ impl Daemon {
|
|||||||
runtime_for_reconciler,
|
runtime_for_reconciler,
|
||||||
packer.clone(),
|
packer.clone(),
|
||||||
guest_reconciler_notify.clone(),
|
guest_reconciler_notify.clone(),
|
||||||
|
kernel_path,
|
||||||
|
initrd_path,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let guest_reconciler_task = guest_reconciler.launch(guest_reconciler_receiver).await?;
|
let guest_reconciler_task = guest_reconciler.launch(guest_reconciler_receiver).await?;
|
||||||
@ -181,3 +185,16 @@ impl Drop for Daemon {
|
|||||||
self.generator_task.abort();
|
self.generator_task.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn detect_guest_file(store: &str, name: &str) -> Result<PathBuf> {
|
||||||
|
let mut path = PathBuf::from(format!("{}/guest/{}", store, name));
|
||||||
|
if path.is_file() {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
path = PathBuf::from(format!("/usr/share/krata/guest/{}", name));
|
||||||
|
if path.is_file() {
|
||||||
|
return Ok(path);
|
||||||
|
}
|
||||||
|
Err(anyhow!("unable to find required guest file: {}", name))
|
||||||
|
}
|
||||||
|
@ -1,20 +1,17 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{hash_map::Entry, HashMap},
|
||||||
|
path::PathBuf,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::Result;
|
||||||
use krata::launchcfg::LaunchPackedFormat;
|
|
||||||
use krata::v1::{
|
use krata::v1::{
|
||||||
common::{
|
common::{Guest, GuestErrorInfo, GuestExitInfo, GuestNetworkState, GuestState, GuestStatus},
|
||||||
guest_image_spec::Image, Guest, GuestErrorInfo, GuestExitInfo, GuestNetworkState,
|
|
||||||
GuestState, GuestStatus, OciImageFormat,
|
|
||||||
},
|
|
||||||
control::GuestChangedEvent,
|
control::GuestChangedEvent,
|
||||||
};
|
};
|
||||||
use krataoci::packer::{service::OciPackerService, OciPackedFormat};
|
use krataoci::packer::service::OciPackerService;
|
||||||
use kratart::{launch::GuestLaunchRequest, GuestInfo, Runtime};
|
use kratart::{GuestInfo, Runtime};
|
||||||
use log::{error, info, trace, warn};
|
use log::{error, info, trace, warn};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
select,
|
select,
|
||||||
@ -33,6 +30,10 @@ use crate::{
|
|||||||
glt::GuestLookupTable,
|
glt::GuestLookupTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use self::start::GuestStarter;
|
||||||
|
|
||||||
|
mod start;
|
||||||
|
|
||||||
const PARALLEL_LIMIT: u32 = 5;
|
const PARALLEL_LIMIT: u32 = 5;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -59,12 +60,15 @@ pub struct GuestReconciler {
|
|||||||
events: DaemonEventContext,
|
events: DaemonEventContext,
|
||||||
runtime: Runtime,
|
runtime: Runtime,
|
||||||
packer: OciPackerService,
|
packer: OciPackerService,
|
||||||
|
kernel_path: PathBuf,
|
||||||
|
initrd_path: PathBuf,
|
||||||
tasks: Arc<Mutex<HashMap<Uuid, GuestReconcilerEntry>>>,
|
tasks: Arc<Mutex<HashMap<Uuid, GuestReconcilerEntry>>>,
|
||||||
guest_reconciler_notify: Sender<Uuid>,
|
guest_reconciler_notify: Sender<Uuid>,
|
||||||
reconcile_lock: Arc<RwLock<()>>,
|
reconcile_lock: Arc<RwLock<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GuestReconciler {
|
impl GuestReconciler {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
glt: GuestLookupTable,
|
glt: GuestLookupTable,
|
||||||
guests: GuestStore,
|
guests: GuestStore,
|
||||||
@ -72,6 +76,8 @@ impl GuestReconciler {
|
|||||||
runtime: Runtime,
|
runtime: Runtime,
|
||||||
packer: OciPackerService,
|
packer: OciPackerService,
|
||||||
guest_reconciler_notify: Sender<Uuid>,
|
guest_reconciler_notify: Sender<Uuid>,
|
||||||
|
kernel_path: PathBuf,
|
||||||
|
initrd_path: PathBuf,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
glt,
|
glt,
|
||||||
@ -79,6 +85,8 @@ impl GuestReconciler {
|
|||||||
events,
|
events,
|
||||||
runtime,
|
runtime,
|
||||||
packer,
|
packer,
|
||||||
|
kernel_path,
|
||||||
|
initrd_path,
|
||||||
tasks: Arc::new(Mutex::new(HashMap::new())),
|
tasks: Arc::new(Mutex::new(HashMap::new())),
|
||||||
guest_reconciler_notify,
|
guest_reconciler_notify,
|
||||||
reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
|
reconcile_lock: Arc::new(RwLock::with_max_readers((), PARALLEL_LIMIT)),
|
||||||
@ -246,76 +254,14 @@ impl GuestReconciler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||||
let Some(ref spec) = guest.spec else {
|
let starter = GuestStarter {
|
||||||
return Err(anyhow!("guest spec not specified"));
|
kernel_path: &self.kernel_path,
|
||||||
|
initrd_path: &self.initrd_path,
|
||||||
|
packer: &self.packer,
|
||||||
|
glt: &self.glt,
|
||||||
|
runtime: &self.runtime,
|
||||||
};
|
};
|
||||||
|
starter.start(uuid, guest).await
|
||||||
let Some(ref image) = spec.image else {
|
|
||||||
return Err(anyhow!("image spec not provided"));
|
|
||||||
};
|
|
||||||
let oci = match image.image {
|
|
||||||
Some(Image::Oci(ref oci)) => oci,
|
|
||||||
None => {
|
|
||||||
return Err(anyhow!("oci spec not specified"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let task = spec.task.as_ref().cloned().unwrap_or_default();
|
|
||||||
|
|
||||||
let image = self
|
|
||||||
.packer
|
|
||||||
.recall(
|
|
||||||
&oci.digest,
|
|
||||||
match oci.format() {
|
|
||||||
OciImageFormat::Unknown => OciPackedFormat::Squashfs,
|
|
||||||
OciImageFormat::Squashfs => OciPackedFormat::Squashfs,
|
|
||||||
OciImageFormat::Erofs => OciPackedFormat::Erofs,
|
|
||||||
OciImageFormat::Tar => {
|
|
||||||
return Err(anyhow!("tar image format is not supported for guests"));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let Some(image) = image else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"image {} in the requested format did not exist",
|
|
||||||
oci.digest
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let info = self
|
|
||||||
.runtime
|
|
||||||
.launch(GuestLaunchRequest {
|
|
||||||
format: LaunchPackedFormat::Squashfs,
|
|
||||||
uuid: Some(uuid),
|
|
||||||
name: if spec.name.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(spec.name.clone())
|
|
||||||
},
|
|
||||||
image,
|
|
||||||
vcpus: spec.vcpus,
|
|
||||||
mem: spec.mem,
|
|
||||||
env: task
|
|
||||||
.environment
|
|
||||||
.iter()
|
|
||||||
.map(|x| (x.key.clone(), x.value.clone()))
|
|
||||||
.collect::<HashMap<_, _>>(),
|
|
||||||
run: empty_vec_optional(task.command.clone()),
|
|
||||||
debug: false,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
self.glt.associate(uuid, info.domid).await;
|
|
||||||
info!("started guest {}", uuid);
|
|
||||||
guest.state = Some(GuestState {
|
|
||||||
status: GuestStatus::Started.into(),
|
|
||||||
network: Some(guestinfo_to_networkstate(&info)),
|
|
||||||
exit_info: None,
|
|
||||||
error_info: None,
|
|
||||||
host: self.glt.host_uuid().to_string(),
|
|
||||||
domid: info.domid,
|
|
||||||
});
|
|
||||||
Ok(GuestReconcilerResult::Changed { rerun: false })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exited(&self, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
async fn exited(&self, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||||
@ -390,15 +336,7 @@ impl GuestReconciler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_vec_optional<T>(value: Vec<T>) -> Option<Vec<T>> {
|
pub fn guestinfo_to_networkstate(info: &GuestInfo) -> GuestNetworkState {
|
||||||
if value.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn guestinfo_to_networkstate(info: &GuestInfo) -> GuestNetworkState {
|
|
||||||
GuestNetworkState {
|
GuestNetworkState {
|
||||||
guest_ipv4: info.guest_ipv4.map(|x| x.to_string()).unwrap_or_default(),
|
guest_ipv4: info.guest_ipv4.map(|x| x.to_string()).unwrap_or_default(),
|
||||||
guest_ipv6: info.guest_ipv6.map(|x| x.to_string()).unwrap_or_default(),
|
guest_ipv6: info.guest_ipv6.map(|x| x.to_string()).unwrap_or_default(),
|
182
crates/daemon/src/reconcile/guest/start.rs
Normal file
182
crates/daemon/src/reconcile/guest/start.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use krata::launchcfg::LaunchPackedFormat;
|
||||||
|
use krata::v1::common::GuestOciImageSpec;
|
||||||
|
use krata::v1::common::{guest_image_spec::Image, Guest, GuestState, GuestStatus, OciImageFormat};
|
||||||
|
use krataoci::packer::{service::OciPackerService, OciPackedFormat};
|
||||||
|
use kratart::{launch::GuestLaunchRequest, Runtime};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
use tokio::fs::{self, File};
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio_tar::Archive;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
glt::GuestLookupTable,
|
||||||
|
reconcile::guest::{guestinfo_to_networkstate, GuestReconcilerResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
// if a kernel is >= 100MB, that's kinda scary.
|
||||||
|
const OCI_SPEC_TAR_FILE_MAX_SIZE: usize = 100 * 1024 * 1024;
|
||||||
|
|
||||||
|
pub struct GuestStarter<'a> {
|
||||||
|
pub kernel_path: &'a Path,
|
||||||
|
pub initrd_path: &'a Path,
|
||||||
|
pub packer: &'a OciPackerService,
|
||||||
|
pub glt: &'a GuestLookupTable,
|
||||||
|
pub runtime: &'a Runtime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GuestStarter<'_> {
|
||||||
|
pub async fn oci_spec_tar_read_file(
|
||||||
|
&self,
|
||||||
|
file: &Path,
|
||||||
|
oci: &GuestOciImageSpec,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
|
if oci.format() != OciImageFormat::Tar {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"oci image spec for {} is required to be in tar format",
|
||||||
|
oci.digest
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = self
|
||||||
|
.packer
|
||||||
|
.recall(&oci.digest, OciPackedFormat::Tar)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(image) = image else {
|
||||||
|
return Err(anyhow!("image {} was not found in tar format", oci.digest));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut archive = Archive::new(File::open(&image.path).await?);
|
||||||
|
let mut entries = archive.entries()?;
|
||||||
|
while let Some(entry) = entries.next().await {
|
||||||
|
let mut entry = entry?;
|
||||||
|
let path = entry.path()?;
|
||||||
|
if entry.header().size()? as usize > OCI_SPEC_TAR_FILE_MAX_SIZE {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"file {} in image {} is larger than the size limit",
|
||||||
|
file.to_string_lossy(),
|
||||||
|
oci.digest
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if path == file {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
entry.read_to_end(&mut buffer).await?;
|
||||||
|
return Ok(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(anyhow!(
|
||||||
|
"unable to find file {} in image {}",
|
||||||
|
file.to_string_lossy(),
|
||||||
|
oci.digest
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start(&self, uuid: Uuid, guest: &mut Guest) -> Result<GuestReconcilerResult> {
|
||||||
|
let Some(ref spec) = guest.spec else {
|
||||||
|
return Err(anyhow!("guest spec not specified"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(ref image) = spec.image else {
|
||||||
|
return Err(anyhow!("image spec not provided"));
|
||||||
|
};
|
||||||
|
let oci = match image.image {
|
||||||
|
Some(Image::Oci(ref oci)) => oci,
|
||||||
|
None => {
|
||||||
|
return Err(anyhow!("oci spec not specified"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let task = spec.task.as_ref().cloned().unwrap_or_default();
|
||||||
|
|
||||||
|
let image = self
|
||||||
|
.packer
|
||||||
|
.recall(
|
||||||
|
&oci.digest,
|
||||||
|
match oci.format() {
|
||||||
|
OciImageFormat::Unknown => OciPackedFormat::Squashfs,
|
||||||
|
OciImageFormat::Squashfs => OciPackedFormat::Squashfs,
|
||||||
|
OciImageFormat::Erofs => OciPackedFormat::Erofs,
|
||||||
|
OciImageFormat::Tar => {
|
||||||
|
return Err(anyhow!("tar image format is not supported for guests"));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let Some(image) = image else {
|
||||||
|
return Err(anyhow!(
|
||||||
|
"image {} in the requested format did not exist",
|
||||||
|
oci.digest
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let kernel = if let Some(ref spec) = spec.kernel {
|
||||||
|
let Some(Image::Oci(ref oci)) = spec.image else {
|
||||||
|
return Err(anyhow!("kernel image spec must be an oci image"));
|
||||||
|
};
|
||||||
|
self.oci_spec_tar_read_file(&PathBuf::from("kernel/image"), oci)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
fs::read(&self.kernel_path).await?
|
||||||
|
};
|
||||||
|
let initrd = if let Some(ref spec) = spec.initrd {
|
||||||
|
let Some(Image::Oci(ref oci)) = spec.image else {
|
||||||
|
return Err(anyhow!("initrd image spec must be an oci image"));
|
||||||
|
};
|
||||||
|
self.oci_spec_tar_read_file(&PathBuf::from("krata/initrd"), oci)
|
||||||
|
.await?
|
||||||
|
} else {
|
||||||
|
fs::read(&self.initrd_path).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
let info = self
|
||||||
|
.runtime
|
||||||
|
.launch(GuestLaunchRequest {
|
||||||
|
format: LaunchPackedFormat::Squashfs,
|
||||||
|
uuid: Some(uuid),
|
||||||
|
name: if spec.name.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(spec.name.clone())
|
||||||
|
},
|
||||||
|
image,
|
||||||
|
kernel,
|
||||||
|
initrd,
|
||||||
|
vcpus: spec.vcpus,
|
||||||
|
mem: spec.mem,
|
||||||
|
env: task
|
||||||
|
.environment
|
||||||
|
.iter()
|
||||||
|
.map(|x| (x.key.clone(), x.value.clone()))
|
||||||
|
.collect::<HashMap<_, _>>(),
|
||||||
|
run: empty_vec_optional(task.command.clone()),
|
||||||
|
debug: false,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
self.glt.associate(uuid, info.domid).await;
|
||||||
|
info!("started guest {}", uuid);
|
||||||
|
guest.state = Some(GuestState {
|
||||||
|
status: GuestStatus::Started.into(),
|
||||||
|
network: Some(guestinfo_to_networkstate(&info)),
|
||||||
|
exit_info: None,
|
||||||
|
error_info: None,
|
||||||
|
host: self.glt.host_uuid().to_string(),
|
||||||
|
domid: info.domid,
|
||||||
|
});
|
||||||
|
Ok(GuestReconcilerResult::Changed { rerun: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn empty_vec_optional<T>(value: Vec<T>) -> Option<Vec<T>> {
|
||||||
|
if value.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(value)
|
||||||
|
}
|
||||||
|
}
|
@ -17,10 +17,14 @@ message Guest {
|
|||||||
message GuestSpec {
|
message GuestSpec {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
GuestImageSpec image = 2;
|
GuestImageSpec image = 2;
|
||||||
uint32 vcpus = 3;
|
// If not specified, defaults to the daemon default kernel.
|
||||||
uint64 mem = 4;
|
GuestImageSpec kernel = 3;
|
||||||
GuestTaskSpec task = 5;
|
// If not specified, defaults to the daemon default initrd.
|
||||||
repeated GuestSpecAnnotation annotations = 6;
|
GuestImageSpec initrd = 4;
|
||||||
|
uint32 vcpus = 5;
|
||||||
|
uint64 mem = 6;
|
||||||
|
GuestTaskSpec task = 7;
|
||||||
|
repeated GuestSpecAnnotation annotations = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GuestImageSpec {
|
message GuestImageSpec {
|
||||||
|
@ -217,6 +217,13 @@ impl OciImageFetcher {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ref digest) = image.digest {
|
||||||
|
if digest != manifest.digest() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
found = Some(manifest);
|
found = Some(manifest);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -240,7 +247,7 @@ impl OciImageFetcher {
|
|||||||
|
|
||||||
let mut client = OciRegistryClient::new(image.registry_url()?, self.platform.clone())?;
|
let mut client = OciRegistryClient::new(image.registry_url()?, self.platform.clone())?;
|
||||||
let (manifest, descriptor, digest) = client
|
let (manifest, descriptor, digest) = client
|
||||||
.get_manifest_with_digest(&image.name, &image.reference)
|
.get_manifest_with_digest(&image.name, image.reference.as_ref(), image.digest.as_ref())
|
||||||
.await?;
|
.await?;
|
||||||
let descriptor = descriptor.unwrap_or_else(|| {
|
let descriptor = descriptor.unwrap_or_else(|| {
|
||||||
DescriptorBuilder::default()
|
DescriptorBuilder::default()
|
||||||
|
@ -2,33 +2,39 @@ use anyhow::Result;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
const DOCKER_HUB_MIRROR: &str = "mirror.gcr.io";
|
|
||||||
const DEFAULT_IMAGE_TAG: &str = "latest";
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct ImageName {
|
pub struct ImageName {
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
pub port: Option<u16>,
|
pub port: Option<u16>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub reference: String,
|
pub reference: Option<String>,
|
||||||
|
pub digest: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ImageName {
|
impl fmt::Display for ImageName {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if DOCKER_HUB_MIRROR == self.hostname && self.port.is_none() {
|
let mut suffix = String::new();
|
||||||
|
|
||||||
|
if let Some(ref reference) = self.reference {
|
||||||
|
suffix.push(':');
|
||||||
|
suffix.push_str(reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref digest) = self.digest {
|
||||||
|
suffix.push('@');
|
||||||
|
suffix.push_str(digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ImageName::DOCKER_HUB_MIRROR == self.hostname && self.port.is_none() {
|
||||||
if self.name.starts_with("library/") {
|
if self.name.starts_with("library/") {
|
||||||
write!(f, "{}:{}", &self.name[8..], self.reference)
|
write!(f, "{}{}", &self.name[8..], suffix)
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}:{}", self.name, self.reference)
|
write!(f, "{}{}", self.name, suffix)
|
||||||
}
|
}
|
||||||
} else if let Some(port) = self.port {
|
} else if let Some(port) = self.port {
|
||||||
write!(
|
write!(f, "{}:{}/{}{}", self.hostname, port, self.name, suffix)
|
||||||
f,
|
|
||||||
"{}:{}/{}:{}",
|
|
||||||
self.hostname, port, self.name, self.reference
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
write!(f, "{}/{}:{}", self.hostname, self.name, self.reference)
|
write!(f, "{}/{}{}", self.hostname, self.name, suffix)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,13 +47,21 @@ impl Default for ImageName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ImageName {
|
impl ImageName {
|
||||||
|
pub const DOCKER_HUB_MIRROR: &'static str = "registry.docker.io";
|
||||||
|
pub const DEFAULT_IMAGE_TAG: &'static str = "latest";
|
||||||
|
|
||||||
pub fn parse(name: &str) -> Result<Self> {
|
pub fn parse(name: &str) -> Result<Self> {
|
||||||
let full_name = name.to_string();
|
let full_name = name.to_string();
|
||||||
let name = full_name.clone();
|
let name = full_name.clone();
|
||||||
let (mut hostname, mut name) = name
|
let (mut hostname, mut name) = name
|
||||||
.split_once('/')
|
.split_once('/')
|
||||||
.map(|x| (x.0.to_string(), x.1.to_string()))
|
.map(|x| (x.0.to_string(), x.1.to_string()))
|
||||||
.unwrap_or_else(|| (DOCKER_HUB_MIRROR.to_string(), format!("library/{}", name)));
|
.unwrap_or_else(|| {
|
||||||
|
(
|
||||||
|
ImageName::DOCKER_HUB_MIRROR.to_string(),
|
||||||
|
format!("library/{}", name),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
// heuristic to find any docker hub image formats
|
// heuristic to find any docker hub image formats
|
||||||
// that may be in the hostname format. for example:
|
// that may be in the hostname format. for example:
|
||||||
@ -55,7 +69,7 @@ impl ImageName {
|
|||||||
// and neither will abc/hello/xyz:latest
|
// and neither will abc/hello/xyz:latest
|
||||||
if !hostname.contains('.') && full_name.chars().filter(|x| *x == '/').count() == 1 {
|
if !hostname.contains('.') && full_name.chars().filter(|x| *x == '/').count() == 1 {
|
||||||
name = format!("{}/{}", hostname, name);
|
name = format!("{}/{}", hostname, name);
|
||||||
hostname = DOCKER_HUB_MIRROR.to_string();
|
hostname = ImageName::DOCKER_HUB_MIRROR.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (hostname, port) = if let Some((hostname, port)) = hostname
|
let (hostname, port) = if let Some((hostname, port)) = hostname
|
||||||
@ -66,15 +80,54 @@ impl ImageName {
|
|||||||
} else {
|
} else {
|
||||||
(hostname, None)
|
(hostname, None)
|
||||||
};
|
};
|
||||||
let (name, reference) = name
|
|
||||||
.split_once(':')
|
let name_has_digest = if name.contains('@') {
|
||||||
.map(|x| (x.0.to_string(), x.1.to_string()))
|
let digest_start = name.chars().position(|c| c == '@');
|
||||||
.unwrap_or((name.to_string(), DEFAULT_IMAGE_TAG.to_string()));
|
let ref_start = name.chars().position(|c| c == ':');
|
||||||
|
if let (Some(digest_start), Some(ref_start)) = (digest_start, ref_start) {
|
||||||
|
digest_start < ref_start
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let (name, digest) = if name_has_digest {
|
||||||
|
name.split_once('@')
|
||||||
|
.map(|(name, digest)| (name.to_string(), Some(digest.to_string())))
|
||||||
|
.unwrap_or_else(|| (name, None))
|
||||||
|
} else {
|
||||||
|
(name, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (name, reference) = if name.contains(':') {
|
||||||
|
name.split_once(':')
|
||||||
|
.map(|(name, reference)| (name.to_string(), Some(reference.to_string())))
|
||||||
|
.unwrap_or((name, None))
|
||||||
|
} else {
|
||||||
|
(name, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (reference, digest) = if let Some(reference) = reference {
|
||||||
|
if let Some(digest) = digest {
|
||||||
|
(Some(reference), Some(digest))
|
||||||
|
} else {
|
||||||
|
reference
|
||||||
|
.split_once('@')
|
||||||
|
.map(|(reff, digest)| (Some(reff.to_string()), Some(digest.to_string())))
|
||||||
|
.unwrap_or_else(|| (Some(reference), None))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(None, digest)
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ImageName {
|
Ok(ImageName {
|
||||||
hostname,
|
hostname,
|
||||||
port,
|
port,
|
||||||
name,
|
name,
|
||||||
reference,
|
reference,
|
||||||
|
digest,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +159,9 @@ impl OciPackerCache {
|
|||||||
let image_name = packed.name.to_string();
|
let image_name = packed.name.to_string();
|
||||||
annotations.insert(ANNOTATION_IMAGE_NAME.to_string(), image_name);
|
annotations.insert(ANNOTATION_IMAGE_NAME.to_string(), image_name);
|
||||||
let image_ref = packed.name.reference.clone();
|
let image_ref = packed.name.reference.clone();
|
||||||
|
if let Some(image_ref) = image_ref {
|
||||||
annotations.insert(ANNOTATION_REF_NAME.to_string(), image_ref);
|
annotations.insert(ANNOTATION_REF_NAME.to_string(), image_ref);
|
||||||
|
}
|
||||||
descriptor.set_annotations(Some(annotations));
|
descriptor.set_annotations(Some(annotations));
|
||||||
manifests.push(descriptor.clone());
|
manifests.push(descriptor.clone());
|
||||||
index.set_manifests(manifests);
|
index.set_manifests(manifests);
|
||||||
|
@ -61,6 +61,10 @@ impl OciPackerService {
|
|||||||
digest: &str,
|
digest: &str,
|
||||||
format: OciPackedFormat,
|
format: OciPackedFormat,
|
||||||
) -> Result<Option<OciPackedImage>> {
|
) -> Result<Option<OciPackedImage>> {
|
||||||
|
if digest.contains('/') || digest.contains('\\') || digest.contains("..") {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
self.cache
|
self.cache
|
||||||
.recall(ImageName::parse("cached:latest")?, digest, format)
|
.recall(ImageName::parse("cached:latest")?, digest, format)
|
||||||
.await
|
.await
|
||||||
|
@ -7,7 +7,7 @@ use reqwest::{Client, RequestBuilder, Response, StatusCode};
|
|||||||
use tokio::{fs::File, io::AsyncWriteExt};
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{progress::OciBoundProgress, schema::OciSchema};
|
use crate::{name::ImageName, progress::OciBoundProgress, schema::OciSchema};
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct OciPlatform {
|
pub struct OciPlatform {
|
||||||
@ -176,7 +176,7 @@ impl OciRegistryClient {
|
|||||||
let url = self.url.join(&format!(
|
let url = self.url.join(&format!(
|
||||||
"/v2/{}/manifests/{}",
|
"/v2/{}/manifests/{}",
|
||||||
name.as_ref(),
|
name.as_ref(),
|
||||||
reference.as_ref()
|
reference.as_ref(),
|
||||||
))?;
|
))?;
|
||||||
let accept = format!(
|
let accept = format!(
|
||||||
"{}, {}, {}, {}",
|
"{}, {}, {}, {}",
|
||||||
@ -202,13 +202,20 @@ impl OciRegistryClient {
|
|||||||
pub async fn get_manifest_with_digest<N: AsRef<str>, R: AsRef<str>>(
|
pub async fn get_manifest_with_digest<N: AsRef<str>, R: AsRef<str>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: N,
|
name: N,
|
||||||
reference: R,
|
reference: Option<R>,
|
||||||
|
digest: Option<N>,
|
||||||
) -> Result<(OciSchema<ImageManifest>, Option<Descriptor>, String)> {
|
) -> Result<(OciSchema<ImageManifest>, Option<Descriptor>, String)> {
|
||||||
let url = self.url.join(&format!(
|
let what = digest
|
||||||
"/v2/{}/manifests/{}",
|
.as_ref()
|
||||||
name.as_ref(),
|
.map(|x| x.as_ref().to_string())
|
||||||
reference.as_ref()
|
.unwrap_or_else(|| {
|
||||||
))?;
|
reference
|
||||||
|
.map(|x| x.as_ref().to_string())
|
||||||
|
.unwrap_or_else(|| ImageName::DEFAULT_IMAGE_TAG.to_string())
|
||||||
|
});
|
||||||
|
let url = self
|
||||||
|
.url
|
||||||
|
.join(&format!("/v2/{}/manifests/{}", name.as_ref(), what,))?;
|
||||||
let accept = format!(
|
let accept = format!(
|
||||||
"{}, {}, {}, {}",
|
"{}, {}, {}, {}",
|
||||||
MediaType::ImageManifest.to_docker_v2s2()?,
|
MediaType::ImageManifest.to_docker_v2s2()?,
|
||||||
@ -239,9 +246,10 @@ impl OciRegistryClient {
|
|||||||
let digest = response
|
let digest = response
|
||||||
.headers()
|
.headers()
|
||||||
.get("Docker-Content-Digest")
|
.get("Docker-Content-Digest")
|
||||||
.ok_or_else(|| anyhow!("fetching manifest did not yield a content digest"))?
|
.and_then(|x| x.to_str().ok())
|
||||||
.to_str()?
|
.map(|x| x.to_string())
|
||||||
.to_string();
|
.or_else(|| digest.map(|x: N| x.as_ref().to_string()))
|
||||||
|
.ok_or_else(|| anyhow!("fetching manifest did not yield a content digest"))?;
|
||||||
let bytes = response.bytes().await?;
|
let bytes = response.bytes().await?;
|
||||||
let manifest = serde_json::from_slice(&bytes)?;
|
let manifest = serde_json::from_slice(&bytes)?;
|
||||||
Ok((OciSchema::new(bytes.to_vec(), manifest), None, digest))
|
Ok((OciSchema::new(bytes.to_vec(), manifest), None, digest))
|
||||||
|
@ -23,6 +23,8 @@ use super::{GuestInfo, GuestState};
|
|||||||
|
|
||||||
pub struct GuestLaunchRequest {
|
pub struct GuestLaunchRequest {
|
||||||
pub format: LaunchPackedFormat,
|
pub format: LaunchPackedFormat,
|
||||||
|
pub kernel: Vec<u8>,
|
||||||
|
pub initrd: Vec<u8>,
|
||||||
pub uuid: Option<Uuid>,
|
pub uuid: Option<Uuid>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub vcpus: u32,
|
pub vcpus: u32,
|
||||||
@ -173,22 +175,22 @@ impl GuestLauncher {
|
|||||||
|
|
||||||
let config = DomainConfig {
|
let config = DomainConfig {
|
||||||
backend_domid: 0,
|
backend_domid: 0,
|
||||||
name: &xen_name,
|
name: xen_name,
|
||||||
max_vcpus: request.vcpus,
|
max_vcpus: request.vcpus,
|
||||||
mem_mb: request.mem,
|
mem_mb: request.mem,
|
||||||
kernel_path: &context.kernel,
|
kernel: request.kernel,
|
||||||
initrd_path: &context.initrd,
|
initrd: request.initrd,
|
||||||
cmdline: &cmdline,
|
cmdline,
|
||||||
use_console_backend: Some("krata-console"),
|
use_console_backend: Some("krata-console".to_string()),
|
||||||
disks: vec![
|
disks: vec![
|
||||||
DomainDisk {
|
DomainDisk {
|
||||||
vdev: "xvda",
|
vdev: "xvda".to_string(),
|
||||||
block: &image_squashfs_loop,
|
block: image_squashfs_loop.clone(),
|
||||||
writable: false,
|
writable: false,
|
||||||
},
|
},
|
||||||
DomainDisk {
|
DomainDisk {
|
||||||
vdev: "xvdb",
|
vdev: "xvdb".to_string(),
|
||||||
block: &cfgblk_squashfs_loop,
|
block: cfgblk_squashfs_loop.clone(),
|
||||||
writable: false,
|
writable: false,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -197,7 +199,7 @@ impl GuestLauncher {
|
|||||||
initialized: false,
|
initialized: false,
|
||||||
}],
|
}],
|
||||||
vifs: vec![DomainNetworkInterface {
|
vifs: vec![DomainNetworkInterface {
|
||||||
mac: &guest_mac_string,
|
mac: guest_mac_string.clone(),
|
||||||
mtu: 1500,
|
mtu: 1500,
|
||||||
bridge: None,
|
bridge: None,
|
||||||
script: None,
|
script: None,
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
use std::{
|
use std::{fs, path::PathBuf, str::FromStr, sync::Arc};
|
||||||
fs,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
str::FromStr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
@ -52,43 +47,17 @@ pub struct GuestInfo {
|
|||||||
pub struct RuntimeContext {
|
pub struct RuntimeContext {
|
||||||
pub autoloop: AutoLoop,
|
pub autoloop: AutoLoop,
|
||||||
pub xen: XenClient,
|
pub xen: XenClient,
|
||||||
pub kernel: String,
|
|
||||||
pub initrd: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RuntimeContext {
|
impl RuntimeContext {
|
||||||
pub async fn new(store: String) -> Result<Self> {
|
pub async fn new() -> Result<Self> {
|
||||||
let mut image_cache_path = PathBuf::from(&store);
|
|
||||||
image_cache_path.push("cache");
|
|
||||||
fs::create_dir_all(&image_cache_path)?;
|
|
||||||
|
|
||||||
let xen = XenClient::open(0).await?;
|
let xen = XenClient::open(0).await?;
|
||||||
image_cache_path.push("image");
|
|
||||||
fs::create_dir_all(&image_cache_path)?;
|
|
||||||
let kernel = RuntimeContext::detect_guest_file(&store, "kernel")?;
|
|
||||||
let initrd = RuntimeContext::detect_guest_file(&store, "initrd")?;
|
|
||||||
|
|
||||||
Ok(RuntimeContext {
|
Ok(RuntimeContext {
|
||||||
autoloop: AutoLoop::new(LoopControl::open()?),
|
autoloop: AutoLoop::new(LoopControl::open()?),
|
||||||
xen,
|
xen,
|
||||||
kernel,
|
|
||||||
initrd,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_guest_file(store: &str, name: &str) -> Result<String> {
|
|
||||||
let mut path = PathBuf::from(format!("{}/guest/{}", store, name));
|
|
||||||
if path.is_file() {
|
|
||||||
return path_as_string(&path);
|
|
||||||
}
|
|
||||||
|
|
||||||
path = PathBuf::from(format!("/usr/share/krata/guest/{}", name));
|
|
||||||
if path.is_file() {
|
|
||||||
return path_as_string(&path);
|
|
||||||
}
|
|
||||||
Err(anyhow!("unable to find required guest file: {}", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list(&self) -> Result<Vec<GuestInfo>> {
|
pub async fn list(&self) -> Result<Vec<GuestInfo>> {
|
||||||
let mut guests: Vec<GuestInfo> = Vec::new();
|
let mut guests: Vec<GuestInfo> = Vec::new();
|
||||||
for domid_candidate in self.xen.store.list("/local/domain").await? {
|
for domid_candidate in self.xen.store.list("/local/domain").await? {
|
||||||
@ -248,16 +217,14 @@ impl RuntimeContext {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
store: Arc<String>,
|
|
||||||
context: RuntimeContext,
|
context: RuntimeContext,
|
||||||
launch_semaphore: Arc<Semaphore>,
|
launch_semaphore: Arc<Semaphore>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
pub async fn new(store: String) -> Result<Self> {
|
pub async fn new() -> Result<Self> {
|
||||||
let context = RuntimeContext::new(store.clone()).await?;
|
let context = RuntimeContext::new().await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
store: Arc::new(store),
|
|
||||||
context,
|
context,
|
||||||
launch_semaphore: Arc::new(Semaphore::new(1)),
|
launch_semaphore: Arc::new(Semaphore::new(1)),
|
||||||
})
|
})
|
||||||
@ -320,12 +287,6 @@ impl Runtime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dupe(&self) -> Result<Runtime> {
|
pub async fn dupe(&self) -> Result<Runtime> {
|
||||||
Runtime::new((*self.store).clone()).await
|
Runtime::new().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_as_string(path: &Path) -> Result<String> {
|
|
||||||
path.to_str()
|
|
||||||
.ok_or_else(|| anyhow!("unable to convert path to string"))
|
|
||||||
.map(|x| x.to_string())
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::{env, process};
|
use std::{env, process};
|
||||||
|
use tokio::fs;
|
||||||
use xenclient::error::Result;
|
use xenclient::error::Result;
|
||||||
use xenclient::{DomainConfig, XenClient};
|
use xenclient::{DomainConfig, XenClient};
|
||||||
|
|
||||||
@ -16,12 +17,12 @@ async fn main() -> Result<()> {
|
|||||||
let client = XenClient::open(0).await?;
|
let client = XenClient::open(0).await?;
|
||||||
let config = DomainConfig {
|
let config = DomainConfig {
|
||||||
backend_domid: 0,
|
backend_domid: 0,
|
||||||
name: "xenclient-test",
|
name: "xenclient-test".to_string(),
|
||||||
max_vcpus: 1,
|
max_vcpus: 1,
|
||||||
mem_mb: 512,
|
mem_mb: 512,
|
||||||
kernel_path: kernel_image_path.as_str(),
|
kernel: fs::read(&kernel_image_path).await?,
|
||||||
initrd_path: initrd_path.as_str(),
|
initrd: fs::read(&initrd_path).await?,
|
||||||
cmdline: "debug elevator=noop",
|
cmdline: "debug elevator=noop".to_string(),
|
||||||
use_console_backend: None,
|
use_console_backend: None,
|
||||||
disks: vec![],
|
disks: vec![],
|
||||||
channels: vec![],
|
channels: vec![],
|
||||||
|
@ -107,17 +107,15 @@ impl ElfImageLoader {
|
|||||||
ElfImageLoader::load_xz(file.as_slice())
|
ElfImageLoader::load_xz(file.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_file_kernel(path: &str) -> Result<ElfImageLoader> {
|
pub fn load_file_kernel(data: &[u8]) -> Result<ElfImageLoader> {
|
||||||
let file = std::fs::read(path)?;
|
for start in find_iter(data, &[0x1f, 0x8b]) {
|
||||||
|
if let Ok(elf) = ElfImageLoader::load_gz(&data[start..]) {
|
||||||
for start in find_iter(file.as_slice(), &[0x1f, 0x8b]) {
|
|
||||||
if let Ok(elf) = ElfImageLoader::load_gz(&file[start..]) {
|
|
||||||
return Ok(elf);
|
return Ok(elf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for start in find_iter(file.as_slice(), &[0xfd, 0x37, 0x7a, 0x58]) {
|
for start in find_iter(data, &[0xfd, 0x37, 0x7a, 0x58]) {
|
||||||
if let Ok(elf) = ElfImageLoader::load_xz(&file[start..]) {
|
if let Ok(elf) = ElfImageLoader::load_xz(&data[start..]) {
|
||||||
return Ok(elf);
|
return Ok(elf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ use boot::BootState;
|
|||||||
use log::{debug, trace, warn};
|
use log::{debug, trace, warn};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
|
|
||||||
use std::fs::read;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -40,60 +39,60 @@ pub struct XenClient {
|
|||||||
call: XenCall,
|
call: XenCall,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct BlockDeviceRef {
|
pub struct BlockDeviceRef {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub major: u32,
|
pub major: u32,
|
||||||
pub minor: u32,
|
pub minor: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DomainDisk<'a> {
|
pub struct DomainDisk {
|
||||||
pub vdev: &'a str,
|
pub vdev: String,
|
||||||
pub block: &'a BlockDeviceRef,
|
pub block: BlockDeviceRef,
|
||||||
pub writable: bool,
|
pub writable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DomainFilesystem<'a> {
|
pub struct DomainFilesystem {
|
||||||
pub path: &'a str,
|
pub path: String,
|
||||||
pub tag: &'a str,
|
pub tag: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DomainNetworkInterface<'a> {
|
pub struct DomainNetworkInterface {
|
||||||
pub mac: &'a str,
|
pub mac: String,
|
||||||
pub mtu: u32,
|
pub mtu: u32,
|
||||||
pub bridge: Option<&'a str>,
|
pub bridge: Option<String>,
|
||||||
pub script: Option<&'a str>,
|
pub script: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DomainChannel {
|
pub struct DomainChannel {
|
||||||
pub typ: String,
|
pub typ: String,
|
||||||
pub initialized: bool,
|
pub initialized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DomainEventChannel<'a> {
|
pub struct DomainEventChannel {
|
||||||
pub name: &'a str,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DomainConfig<'a> {
|
pub struct DomainConfig {
|
||||||
pub backend_domid: u32,
|
pub backend_domid: u32,
|
||||||
pub name: &'a str,
|
pub name: String,
|
||||||
pub max_vcpus: u32,
|
pub max_vcpus: u32,
|
||||||
pub mem_mb: u64,
|
pub mem_mb: u64,
|
||||||
pub kernel_path: &'a str,
|
pub kernel: Vec<u8>,
|
||||||
pub initrd_path: &'a str,
|
pub initrd: Vec<u8>,
|
||||||
pub cmdline: &'a str,
|
pub cmdline: String,
|
||||||
pub disks: Vec<DomainDisk<'a>>,
|
pub disks: Vec<DomainDisk>,
|
||||||
pub use_console_backend: Option<&'a str>,
|
pub use_console_backend: Option<String>,
|
||||||
pub channels: Vec<DomainChannel>,
|
pub channels: Vec<DomainChannel>,
|
||||||
pub vifs: Vec<DomainNetworkInterface<'a>>,
|
pub vifs: Vec<DomainNetworkInterface>,
|
||||||
pub filesystems: Vec<DomainFilesystem<'a>>,
|
pub filesystems: Vec<DomainFilesystem>,
|
||||||
pub event_channels: Vec<DomainEventChannel<'a>>,
|
pub event_channels: Vec<DomainEventChannel>,
|
||||||
pub extra_keys: Vec<(String, String)>,
|
pub extra_keys: Vec<(String, String)>,
|
||||||
pub extra_rw_paths: Vec<String>,
|
pub extra_rw_paths: Vec<String>,
|
||||||
}
|
}
|
||||||
@ -117,7 +116,7 @@ impl XenClient {
|
|||||||
Ok(XenClient { store, call })
|
Ok(XenClient { store, call })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create(&self, config: &DomainConfig<'_>) -> Result<CreatedDomain> {
|
pub async fn create(&self, config: &DomainConfig) -> Result<CreatedDomain> {
|
||||||
let mut domain = CreateDomain {
|
let mut domain = CreateDomain {
|
||||||
max_vcpus: config.max_vcpus,
|
max_vcpus: config.max_vcpus,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -143,7 +142,7 @@ impl XenClient {
|
|||||||
&self,
|
&self,
|
||||||
domid: u32,
|
domid: u32,
|
||||||
domain: &CreateDomain,
|
domain: &CreateDomain,
|
||||||
config: &DomainConfig<'_>,
|
config: &DomainConfig,
|
||||||
) -> Result<CreatedDomain> {
|
) -> Result<CreatedDomain> {
|
||||||
trace!(
|
trace!(
|
||||||
"XenClient init domid={} domain={:?} config={:?}",
|
"XenClient init domid={} domain={:?} config={:?}",
|
||||||
@ -237,9 +236,9 @@ impl XenClient {
|
|||||||
&Uuid::from_bytes(domain.handle).to_string(),
|
&Uuid::from_bytes(domain.handle).to_string(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
tx.write_string(format!("{}/name", dom_path).as_str(), config.name)
|
tx.write_string(format!("{}/name", dom_path).as_str(), &config.name)
|
||||||
.await?;
|
.await?;
|
||||||
tx.write_string(format!("{}/name", vm_path).as_str(), config.name)
|
tx.write_string(format!("{}/name", vm_path).as_str(), &config.name)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for (key, value) in &config.extra_keys {
|
for (key, value) in &config.extra_keys {
|
||||||
@ -257,7 +256,7 @@ impl XenClient {
|
|||||||
|
|
||||||
self.call.set_max_vcpus(domid, config.max_vcpus).await?;
|
self.call.set_max_vcpus(domid, config.max_vcpus).await?;
|
||||||
self.call.set_max_mem(domid, config.mem_mb * 1024).await?;
|
self.call.set_max_mem(domid, config.mem_mb * 1024).await?;
|
||||||
let image_loader = ElfImageLoader::load_file_kernel(config.kernel_path)?;
|
let image_loader = ElfImageLoader::load_file_kernel(&config.kernel)?;
|
||||||
|
|
||||||
let xenstore_evtchn: u32;
|
let xenstore_evtchn: u32;
|
||||||
let xenstore_mfn: u64;
|
let xenstore_mfn: u64;
|
||||||
@ -270,18 +269,17 @@ impl XenClient {
|
|||||||
let mut arch = Box::new(X86BootSetup::new()) as Box<dyn ArchBootSetup + Send + Sync>;
|
let mut arch = Box::new(X86BootSetup::new()) as Box<dyn ArchBootSetup + Send + Sync>;
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
let mut arch = Box::new(Arm64BootSetup::new()) as Box<dyn ArchBootSetup + Send + Sync>;
|
let mut arch = Box::new(Arm64BootSetup::new()) as Box<dyn ArchBootSetup + Send + Sync>;
|
||||||
let initrd = read(config.initrd_path)?;
|
|
||||||
state = boot
|
state = boot
|
||||||
.initialize(
|
.initialize(
|
||||||
&mut arch,
|
&mut arch,
|
||||||
&image_loader,
|
&image_loader,
|
||||||
initrd.as_slice(),
|
&config.initrd,
|
||||||
config.max_vcpus,
|
config.max_vcpus,
|
||||||
config.mem_mb,
|
config.mem_mb,
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
boot.boot(&mut arch, &mut state, config.cmdline).await?;
|
boot.boot(&mut arch, &mut state, &config.cmdline).await?;
|
||||||
xenstore_evtchn = state.store_evtchn;
|
xenstore_evtchn = state.store_evtchn;
|
||||||
xenstore_mfn = boot.phys.p2m[state.xenstore_segment.pfn as usize];
|
xenstore_mfn = boot.phys.p2m[state.xenstore_segment.pfn as usize];
|
||||||
p2m = boot.phys.p2m;
|
p2m = boot.phys.p2m;
|
||||||
@ -291,19 +289,9 @@ impl XenClient {
|
|||||||
let tx = self.store.transaction().await?;
|
let tx = self.store.transaction().await?;
|
||||||
tx.write_string(format!("{}/image/os_type", vm_path).as_str(), "linux")
|
tx.write_string(format!("{}/image/os_type", vm_path).as_str(), "linux")
|
||||||
.await?;
|
.await?;
|
||||||
tx.write_string(
|
|
||||||
format!("{}/image/kernel", vm_path).as_str(),
|
|
||||||
config.kernel_path,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
tx.write_string(
|
|
||||||
format!("{}/image/ramdisk", vm_path).as_str(),
|
|
||||||
config.initrd_path,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
tx.write_string(
|
tx.write_string(
|
||||||
format!("{}/image/cmdline", vm_path).as_str(),
|
format!("{}/image/cmdline", vm_path).as_str(),
|
||||||
config.cmdline,
|
&config.cmdline,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -352,7 +340,8 @@ impl XenClient {
|
|||||||
&DomainChannel {
|
&DomainChannel {
|
||||||
typ: config
|
typ: config
|
||||||
.use_console_backend
|
.use_console_backend
|
||||||
.unwrap_or("xenconsoled")
|
.clone()
|
||||||
|
.unwrap_or("xenconsoled".to_string())
|
||||||
.to_string(),
|
.to_string(),
|
||||||
initialized: true,
|
initialized: true,
|
||||||
},
|
},
|
||||||
@ -429,7 +418,7 @@ impl XenClient {
|
|||||||
.await?;
|
.await?;
|
||||||
let channel_path = format!("{}/evtchn/{}", dom_path, channel.name);
|
let channel_path = format!("{}/evtchn/{}", dom_path, channel.name);
|
||||||
self.store
|
self.store
|
||||||
.write_string(&format!("{}/name", channel_path), channel.name)
|
.write_string(&format!("{}/name", channel_path), &channel.name)
|
||||||
.await?;
|
.await?;
|
||||||
self.store
|
self.store
|
||||||
.write_string(&format!("{}/channel", channel_path), &id.to_string())
|
.write_string(&format!("{}/channel", channel_path), &id.to_string())
|
||||||
@ -447,7 +436,7 @@ impl XenClient {
|
|||||||
backend_domid: u32,
|
backend_domid: u32,
|
||||||
domid: u32,
|
domid: u32,
|
||||||
index: usize,
|
index: usize,
|
||||||
disk: &DomainDisk<'_>,
|
disk: &DomainDisk,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let id = (202 << 8) | (index << 4) as u64;
|
let id = (202 << 8) | (index << 4) as u64;
|
||||||
let backend_items: Vec<(&str, String)> = vec![
|
let backend_items: Vec<(&str, String)> = vec![
|
||||||
@ -567,7 +556,7 @@ impl XenClient {
|
|||||||
backend_domid: u32,
|
backend_domid: u32,
|
||||||
domid: u32,
|
domid: u32,
|
||||||
index: usize,
|
index: usize,
|
||||||
filesystem: &DomainFilesystem<'_>,
|
filesystem: &DomainFilesystem,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let id = 90 + index as u64;
|
let id = 90 + index as u64;
|
||||||
let backend_items: Vec<(&str, String)> = vec![
|
let backend_items: Vec<(&str, String)> = vec![
|
||||||
@ -605,7 +594,7 @@ impl XenClient {
|
|||||||
backend_domid: u32,
|
backend_domid: u32,
|
||||||
domid: u32,
|
domid: u32,
|
||||||
index: usize,
|
index: usize,
|
||||||
vif: &DomainNetworkInterface<'_>,
|
vif: &DomainNetworkInterface,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let id = 20 + index as u64;
|
let id = 20 + index as u64;
|
||||||
let mut backend_items: Vec<(&str, String)> = vec![
|
let mut backend_items: Vec<(&str, String)> = vec![
|
||||||
@ -619,12 +608,12 @@ impl XenClient {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if vif.bridge.is_some() {
|
if vif.bridge.is_some() {
|
||||||
backend_items.extend_from_slice(&[("bridge", vif.bridge.unwrap().to_string())]);
|
backend_items.extend_from_slice(&[("bridge", vif.bridge.clone().unwrap())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if vif.script.is_some() {
|
if vif.script.is_some() {
|
||||||
backend_items.extend_from_slice(&[
|
backend_items.extend_from_slice(&[
|
||||||
("script", vif.script.unwrap().to_string()),
|
("script", vif.script.clone().unwrap()),
|
||||||
("hotplug-status", "".to_string()),
|
("hotplug-status", "".to_string()),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
|
Reference in New Issue
Block a user