1use std::collections::HashMap;
2use std::fs;
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use advmac::MacAddr6;
7use anyhow::{anyhow, Result};
8use tokio::sync::Semaphore;
9use uuid::Uuid;
10
11use krata::launchcfg::{
12 LaunchInfo, LaunchNetwork, LaunchNetworkIpv4, LaunchNetworkIpv6, LaunchNetworkResolver,
13 LaunchPackedFormat, LaunchRoot,
14};
15use krataoci::packer::OciPackedImage;
16pub use xenclient::{
17 pci::PciBdf, DomainPciDevice as PciDevice, DomainPciRdmReservePolicy as PciRdmReservePolicy,
18};
19use xenclient::{DomainChannel, DomainConfig, DomainDisk, DomainNetworkInterface};
20use xenplatform::domain::BaseDomainConfig;
21
22use crate::cfgblk::ConfigBlock;
23use crate::RuntimeContext;
24
25use super::{ZoneInfo, ZoneState};
26
27pub struct ZoneLaunchRequest {
28 pub format: LaunchPackedFormat,
29 pub kernel: Vec<u8>,
30 pub initrd: Vec<u8>,
31 pub uuid: Option<Uuid>,
32 pub name: Option<String>,
33 pub target_cpus: u32,
34 pub max_cpus: u32,
35 pub target_memory: u64,
36 pub max_memory: u64,
37 pub env: HashMap<String, String>,
38 pub run: Option<Vec<String>>,
39 pub pcis: Vec<PciDevice>,
40 pub kernel_verbose: bool,
41 pub kernel_cmdline_append: String,
42 pub image: OciPackedImage,
43 pub addons_image: Option<PathBuf>,
44 pub network: ZoneLaunchNetwork,
45}
46
47pub struct ZoneLaunchNetwork {
48 pub ipv4: String,
49 pub ipv4_prefix: u8,
50 pub ipv6: String,
51 pub ipv6_prefix: u8,
52 pub gateway_ipv4: String,
53 pub gateway_ipv6: String,
54 pub zone_mac: MacAddr6,
55 pub nameservers: Vec<String>,
56}
57
58pub struct ZoneLauncher {
59 pub launch_semaphore: Arc<Semaphore>,
60}
61
62impl ZoneLauncher {
63 pub fn new(launch_semaphore: Arc<Semaphore>) -> Result<Self> {
64 Ok(Self { launch_semaphore })
65 }
66
67 pub async fn launch(
68 &mut self,
69 context: &RuntimeContext,
70 request: ZoneLaunchRequest,
71 ) -> Result<ZoneInfo> {
72 let uuid = request.uuid.unwrap_or_else(Uuid::new_v4);
73 let xen_name = format!("krata-{uuid}");
74 let _launch_permit = self.launch_semaphore.acquire().await?;
75 let launch_config = LaunchInfo {
76 root: LaunchRoot {
77 format: request.format.clone(),
78 },
79 hostname: Some(
80 request
81 .name
82 .as_ref()
83 .map(|x| x.to_string())
84 .unwrap_or_else(|| format!("krata-{}", uuid)),
85 ),
86 network: Some(LaunchNetwork {
87 link: "eth0".to_string(),
88 ipv4: LaunchNetworkIpv4 {
89 address: format!("{}/{}", request.network.ipv4, request.network.ipv4_prefix),
90 gateway: request.network.gateway_ipv4,
91 },
92 ipv6: LaunchNetworkIpv6 {
93 address: format!("{}/{}", request.network.ipv6, request.network.ipv6_prefix),
94 gateway: request.network.gateway_ipv6.to_string(),
95 },
96 resolver: LaunchNetworkResolver {
97 nameservers: request.network.nameservers,
98 },
99 }),
100 env: request.env,
101 run: request.run,
102 };
103
104 let cfgblk = ConfigBlock::new(&uuid, request.image.clone())?;
105 let cfgblk_file = cfgblk.file.clone();
106 let cfgblk_dir = cfgblk.dir.clone();
107 tokio::task::spawn_blocking(move || cfgblk.build(&launch_config)).await??;
108
109 let image_squashfs_path = request
110 .image
111 .path
112 .to_str()
113 .ok_or_else(|| anyhow!("failed to convert image path to string"))?;
114
115 let cfgblk_dir_path = cfgblk_dir
116 .to_str()
117 .ok_or_else(|| anyhow!("failed to convert cfgblk directory path to string"))?;
118 let cfgblk_squashfs_path = cfgblk_file
119 .to_str()
120 .ok_or_else(|| anyhow!("failed to convert cfgblk squashfs path to string"))?;
121 let addons_squashfs_path = request
122 .addons_image
123 .map(|x| x.to_str().map(|x| x.to_string()))
124 .map(|x| {
125 Some(x.ok_or_else(|| anyhow!("failed to convert addons squashfs path to string")))
126 })
127 .unwrap_or(None);
128
129 let addons_squashfs_path = if let Some(path) = addons_squashfs_path {
130 Some(path?)
131 } else {
132 None
133 };
134
135 let image_squashfs_loop = context.autoloop.loopify(image_squashfs_path)?;
136 let cfgblk_squashfs_loop = context.autoloop.loopify(cfgblk_squashfs_path)?;
137 let addons_squashfs_loop = if let Some(ref addons_squashfs_path) = addons_squashfs_path {
138 Some(context.autoloop.loopify(addons_squashfs_path)?)
139 } else {
140 None
141 };
142 let mut cmdline_options = ["console=hvc0"].to_vec();
143 if !request.kernel_verbose {
144 cmdline_options.push("quiet");
145 }
146
147 if !request.kernel_cmdline_append.is_empty() {
148 cmdline_options.push(&request.kernel_cmdline_append);
149 }
150
151 let cmdline = cmdline_options.join(" ");
152
153 let zone_mac_string = request.network.zone_mac.to_string().replace('-', ":");
154
155 let mut disks = vec![
156 DomainDisk {
157 vdev: "xvda".to_string(),
158 block: image_squashfs_loop.clone(),
159 writable: false,
160 },
161 DomainDisk {
162 vdev: "xvdb".to_string(),
163 block: cfgblk_squashfs_loop.clone(),
164 writable: false,
165 },
166 ];
167
168 if let Some(ref addons) = addons_squashfs_loop {
169 disks.push(DomainDisk {
170 vdev: "xvdc".to_string(),
171 block: addons.clone(),
172 writable: false,
173 });
174 }
175
176 let mut loops = vec![
177 format!("{}:{}:none", image_squashfs_loop.path, image_squashfs_path),
178 format!(
179 "{}:{}:{}",
180 cfgblk_squashfs_loop.path, cfgblk_squashfs_path, cfgblk_dir_path
181 ),
182 ];
183
184 if let Some(ref addons) = addons_squashfs_loop {
185 loops.push(format!(
186 "{}:{}:none",
187 addons.path,
188 addons_squashfs_path
189 .clone()
190 .ok_or_else(|| anyhow!("addons squashfs path missing"))?
191 ));
192 }
193
194 let mut extra_keys = vec![
195 ("krata/uuid".to_string(), uuid.to_string()),
196 ("krata/loops".to_string(), loops.join(",")),
197 ];
198
199 if let Some(name) = request.name.as_ref() {
200 extra_keys.push(("krata/name".to_string(), name.clone()));
201 }
202
203 let config = DomainConfig {
204 base: BaseDomainConfig {
205 max_vcpus: request.max_cpus,
206 target_vcpus: request.target_cpus,
207 max_mem_mb: request.max_memory,
208 target_mem_mb: request.target_memory,
209 kernel: request.kernel,
210 initrd: request.initrd,
211 cmdline,
212 uuid,
213 owner_domid: 0,
214 enable_iommu: !request.pcis.is_empty(),
215 },
216 backend_domid: 0,
217 name: xen_name,
218 swap_console_backend: Some("krata-console".to_string()),
219 disks,
220 channels: vec![DomainChannel {
221 typ: "krata-channel".to_string(),
222 initialized: false,
223 }],
224 vifs: vec![DomainNetworkInterface {
225 mac: zone_mac_string.clone(),
226 mtu: 1500,
227 bridge: None,
228 script: None,
229 }],
230 pcis: request.pcis.clone(),
231 filesystems: vec![],
232 extra_keys,
233 extra_rw_paths: vec!["krata/zone".to_string()],
234 };
235 match context.xen.create(&config).await {
236 Ok(created) => Ok(ZoneInfo {
237 name: request.name.as_ref().map(|x| x.to_string()),
238 uuid,
239 domid: created.domid,
240 image: request.image.digest,
241 loops: vec![],
242 state: ZoneState { exit_code: None },
243 }),
244 Err(error) => {
245 let _ = context.autoloop.unloop(&image_squashfs_loop.path).await;
246 let _ = context.autoloop.unloop(&cfgblk_squashfs_loop.path).await;
247 let _ = fs::remove_dir(&cfgblk_dir);
248 Err(error.into())
249 }
250 }
251 }
252}