1use std::collections::HashMap;
8use std::fs::OpenOptions;
9use std::io::{Error, ErrorKind, Result};
10use std::os::fd::FromRawFd;
11use std::path::{Path, PathBuf};
12use std::sync::atomic::{AtomicU32, Ordering};
13use std::sync::{Arc, Mutex, MutexGuard};
14
15use nydus_api::{
16 BlobCacheEntry, BlobCacheList, BlobCacheObjectId, ConfigV2, BLOB_CACHE_TYPE_DATA_BLOB,
17 BLOB_CACHE_TYPE_META_BLOB,
18};
19use nydus_rafs::metadata::layout::v6::{EROFS_BLOCK_BITS_12, EROFS_BLOCK_SIZE_4096};
20use nydus_rafs::metadata::{RafsBlobExtraInfo, RafsSuper, RafsSuperFlags};
21use nydus_storage::cache::BlobCache;
22use nydus_storage::device::BlobInfo;
23use nydus_storage::factory::BLOB_FACTORY;
24use tokio_uring::buf::IoBufMut;
25use tokio_uring::fs::File;
26
27const ID_SPLITTER: &str = "/";
28
29pub fn generate_blob_key(domain_id: &str, blob_id: &str) -> String {
31 if domain_id.is_empty() {
32 blob_id.to_string()
33 } else {
34 format!("{}{}{}", domain_id, ID_SPLITTER, blob_id)
35 }
36}
37
38pub struct MetaBlobConfig {
40 blob_id: String,
41 scoped_blob_id: String,
42 path: PathBuf,
43 config: Arc<ConfigV2>,
44 blobs: Mutex<Vec<Arc<DataBlobConfig>>>,
45 blob_extra_infos: HashMap<String, RafsBlobExtraInfo>,
46 is_tarfs_mode: bool,
47}
48
49impl MetaBlobConfig {
50 pub fn blob_id(&self) -> &str {
52 &self.blob_id
53 }
54
55 pub fn path(&self) -> &Path {
57 &self.path
58 }
59
60 pub fn config_v2(&self) -> &Arc<ConfigV2> {
62 &self.config
63 }
64
65 pub fn get_blobs(&self) -> Vec<Arc<DataBlobConfig>> {
66 self.blobs.lock().unwrap().clone()
67 }
68
69 pub fn get_blob_extra_info(&self, blob_id: &str) -> Option<&RafsBlobExtraInfo> {
71 self.blob_extra_infos.get(blob_id)
72 }
73
74 pub fn is_tarfs_mode(&self) -> bool {
76 self.is_tarfs_mode
77 }
78
79 fn add_data_blob(&self, blob: Arc<DataBlobConfig>) {
80 self.blobs.lock().unwrap().push(blob);
81 }
82}
83
84pub struct DataBlobConfig {
86 scoped_blob_id: String,
87 blob_info: Arc<BlobInfo>,
88 config: Arc<ConfigV2>,
89 ref_count: AtomicU32,
90}
91
92impl DataBlobConfig {
93 pub fn blob_info(&self) -> &Arc<BlobInfo> {
95 &self.blob_info
96 }
97
98 pub fn config_v2(&self) -> &Arc<ConfigV2> {
100 &self.config
101 }
102}
103
104#[derive(Clone)]
106pub enum BlobConfig {
107 MetaBlob(Arc<MetaBlobConfig>),
109 DataBlob(Arc<DataBlobConfig>),
111}
112
113impl BlobConfig {
114 pub fn config_v2(&self) -> &Arc<ConfigV2> {
116 match self {
117 BlobConfig::MetaBlob(v) => v.config_v2(),
118 BlobConfig::DataBlob(v) => v.config_v2(),
119 }
120 }
121
122 fn new_data_blob(domain_id: String, blob_info: Arc<BlobInfo>, config: Arc<ConfigV2>) -> Self {
123 let scoped_blob_id = generate_blob_key(&domain_id, &blob_info.blob_id());
124
125 BlobConfig::DataBlob(Arc::new(DataBlobConfig {
126 blob_info,
127 scoped_blob_id,
128 config,
129 ref_count: AtomicU32::new(1),
130 }))
131 }
132
133 fn new_meta_blob(
134 domain_id: String,
135 blob_id: String,
136 path: PathBuf,
137 config: Arc<ConfigV2>,
138 blob_extra_infos: HashMap<String, RafsBlobExtraInfo>,
139 is_tarfs_mode: bool,
140 ) -> Self {
141 let scoped_blob_id = generate_blob_key(&domain_id, &blob_id);
142
143 BlobConfig::MetaBlob(Arc::new(MetaBlobConfig {
144 blob_id,
145 scoped_blob_id,
146 path,
147 config,
148 blobs: Mutex::new(Vec::new()),
149 blob_extra_infos,
150 is_tarfs_mode,
151 }))
152 }
153
154 fn key(&self) -> &str {
155 match self {
156 BlobConfig::MetaBlob(o) => &o.scoped_blob_id,
157 BlobConfig::DataBlob(o) => &o.scoped_blob_id,
158 }
159 }
160
161 fn meta_config(&self) -> Option<Arc<MetaBlobConfig>> {
162 match self {
163 BlobConfig::MetaBlob(o) => Some(o.clone()),
164 BlobConfig::DataBlob(_o) => None,
165 }
166 }
167}
168
169#[derive(Default)]
170struct BlobCacheState {
171 id_to_config_map: HashMap<String, BlobConfig>,
172}
173
174impl BlobCacheState {
175 fn new() -> Self {
176 Self::default()
177 }
178
179 fn try_add(&mut self, config: BlobConfig) -> Result<()> {
180 let key = config.key();
181
182 if let Some(entry) = self.id_to_config_map.get(key) {
183 match entry {
184 BlobConfig::MetaBlob(_o) => {
185 return Err(Error::new(
187 ErrorKind::AlreadyExists,
188 "blob_cache: bootstrap blob already exists",
189 ));
190 }
191 BlobConfig::DataBlob(o) => {
192 o.ref_count.fetch_add(1, Ordering::AcqRel);
194 }
195 }
196 } else {
197 self.id_to_config_map.insert(key.to_owned(), config);
198 }
199
200 Ok(())
201 }
202
203 fn remove(&mut self, param: &BlobCacheObjectId) -> Result<()> {
204 if param.blob_id.is_empty() && !param.domain_id.is_empty() {
205 let scoped_blob_prefix = format!("{}{}", param.domain_id, ID_SPLITTER);
207 self.id_to_config_map.retain(|_k, v| match v {
208 BlobConfig::MetaBlob(o) => !o.scoped_blob_id.starts_with(&scoped_blob_prefix),
209 BlobConfig::DataBlob(o) => !o.scoped_blob_id.starts_with(&scoped_blob_prefix),
210 });
211 } else {
212 let mut data_blobs = Vec::new();
213 let mut is_meta = false;
214 let scoped_blob_prefix = generate_blob_key(¶m.domain_id, ¶m.blob_id);
215
216 match self.id_to_config_map.get(&scoped_blob_prefix) {
217 None => return Err(enoent!("blob_cache: cache entry not found")),
218 Some(BlobConfig::MetaBlob(o)) => {
219 is_meta = true;
220 data_blobs = o.blobs.lock().unwrap().clone();
221 }
222 Some(BlobConfig::DataBlob(o)) => {
223 data_blobs.push(o.clone());
224 }
225 }
226
227 for entry in data_blobs {
228 if entry.ref_count.fetch_sub(1, Ordering::AcqRel) == 1 {
229 self.id_to_config_map.remove(&entry.scoped_blob_id);
230 }
231 }
232
233 if is_meta {
234 self.id_to_config_map.remove(&scoped_blob_prefix);
235 }
236 }
237
238 Ok(())
239 }
240
241 fn get(&self, key: &str) -> Option<BlobConfig> {
242 self.id_to_config_map.get(key).cloned()
243 }
244}
245
246#[derive(Default)]
248pub struct BlobCacheMgr {
249 state: Mutex<BlobCacheState>,
250}
251
252impl BlobCacheMgr {
253 pub fn new() -> Self {
255 BlobCacheMgr {
256 state: Mutex::new(BlobCacheState::new()),
257 }
258 }
259
260 pub fn add_blob_entry(&self, entry: &BlobCacheEntry) -> Result<()> {
270 match entry.blob_type.as_str() {
271 BLOB_CACHE_TYPE_META_BLOB => {
272 let (path, config) = self.get_meta_info(entry)?;
273 self.add_meta_object(&entry.domain_id, &entry.blob_id, path, config)
274 .inspect_err(|_e| {
275 warn!(
276 "blob_cache: failed to add cache entry for meta blob: {:?}",
277 entry
278 );
279 })
280 }
281 BLOB_CACHE_TYPE_DATA_BLOB => Err(einval!(format!(
282 "blob_cache: invalid data blob cache entry: {:?}",
283 entry
284 ))),
285 _ => Err(einval!(format!(
286 "blob_cache: invalid blob cache entry, {:?}",
287 entry
288 ))),
289 }
290 }
291
292 pub fn add_blob_list(&self, blobs: &BlobCacheList) -> Result<()> {
296 for entry in blobs.blobs.iter() {
297 self.add_blob_entry(entry)?;
298 }
299
300 Ok(())
301 }
302
303 pub fn remove_blob_entry(&self, param: &BlobCacheObjectId) -> Result<()> {
305 self.get_state().remove(param)
306 }
307
308 pub fn get_config(&self, key: &str) -> Option<BlobConfig> {
310 self.get_state().get(key)
311 }
312
313 #[inline]
314 fn get_state(&self) -> MutexGuard<'_, BlobCacheState> {
315 self.state.lock().unwrap()
316 }
317
318 fn get_meta_info(&self, entry: &BlobCacheEntry) -> Result<(PathBuf, Arc<ConfigV2>)> {
319 let config = entry
320 .blob_config
321 .as_ref()
322 .ok_or_else(|| einval!("blob_cache: missing blob cache configuration information"))?;
323
324 if entry.blob_id.contains(ID_SPLITTER) {
325 return Err(einval!("blob_cache: `blob_id` for meta blob is invalid"));
326 } else if entry.domain_id.contains(ID_SPLITTER) {
327 return Err(einval!("blob_cache: `domain_id` for meta blob is invalid"));
328 }
329
330 let path = config.metadata_path.clone().unwrap_or_default();
331 if path.is_empty() {
332 return Err(einval!(
333 "blob_cache: `config.metadata_path` for meta blob is empty"
334 ));
335 }
336 let path = Path::new(&path).canonicalize().map_err(|_e| {
337 einval!(format!(
338 "blob_cache: `config.metadata_path={}` for meta blob is invalid",
339 path
340 ))
341 })?;
342 if !path.is_file() {
343 return Err(einval!(
344 "blob_cache: `config.metadata_path` for meta blob is not a file"
345 ));
346 }
347
348 if config.cache.is_fscache() {
350 let cache_config = config.cache.get_fscache_config()?;
352 let path2 = Path::new(&cache_config.work_dir);
353 let path2 = path2
354 .canonicalize()
355 .map_err(|_e| eio!("blob_cache: `config.cache_config.work_dir` is invalid"))?;
356 if !path2.is_dir() {
357 return Err(einval!(
358 "blob_cache: `config.cache_config.work_dir` is not a directory"
359 ));
360 }
361 } else if config.cache.is_filecache() {
362 let cache_config = config.cache.get_filecache_config()?;
364 let path2 = Path::new(&cache_config.work_dir);
365 let path2 = path2
366 .canonicalize()
367 .map_err(|_e| eio!("blob_cache: `config.cache_config.work_dir` is invalid"))?;
368 if !path2.is_dir() {
369 return Err(einval!(
370 "blob_cache: `config.cache_config.work_dir` is not a directory"
371 ));
372 }
373 } else {
374 return Err(einval!("blob_cache: unknown cache type"));
375 }
376
377 let config: Arc<ConfigV2> = Arc::new(config.into());
378 config.internal.set_blob_accessible(true);
379
380 Ok((path, config))
381 }
382
383 fn add_meta_object(
384 &self,
385 domain_id: &str,
386 id: &str,
387 path: PathBuf,
388 config: Arc<ConfigV2>,
389 ) -> Result<()> {
390 let (rs, _) = RafsSuper::load_from_file(&path, config.clone(), false)?;
391 if rs.meta.is_v5() {
392 return Err(einval!("blob_cache: RAFSv5 image is not supported"));
393 }
394
395 let blob_extra_infos = rs.superblock.get_blob_extra_infos()?;
396 let meta = BlobConfig::new_meta_blob(
397 domain_id.to_string(),
398 id.to_string(),
399 path,
400 config,
401 blob_extra_infos,
402 rs.meta.flags.contains(RafsSuperFlags::TARTFS_MODE),
403 );
404 let meta_obj = meta.meta_config().unwrap();
406 let mut state = self.get_state();
407 state.try_add(meta)?;
408
409 for bi in rs.superblock.get_blob_infos() {
411 debug!(
412 "blob_cache: add data blob {} to domain {}",
413 &bi.blob_id(),
414 domain_id
415 );
416 let data_blob =
417 BlobConfig::new_data_blob(domain_id.to_string(), bi, meta_obj.config.clone());
418 let data_blob_config = match &data_blob {
419 BlobConfig::DataBlob(entry) => entry.clone(),
420 _ => panic!("blob_cache: internal error"),
421 };
422
423 if let Err(e) = state.try_add(data_blob) {
424 let id = BlobCacheObjectId {
426 domain_id: domain_id.to_string(),
427 blob_id: id.to_string(),
428 };
429 let _ = state.remove(&id);
430 return Err(e);
431 }
432
433 meta_obj.add_data_blob(data_blob_config);
435 }
436
437 Ok(())
438 }
439}
440
441pub struct MetaBlob {
443 file: File,
444 size: u64,
445}
446
447impl MetaBlob {
448 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
450 let file = OpenOptions::new()
451 .read(true)
452 .write(false)
453 .open(path.as_ref())
454 .inspect_err(|_e| {
455 warn!(
456 "blob_cache: failed to open metadata blob {}",
457 path.as_ref().display()
458 );
459 })?;
460 let md = file.metadata().inspect_err(|_e| {
461 warn!(
462 "blob_cache: failed to get metadata about metadata blob {}",
463 path.as_ref().display()
464 );
465 })?;
466 let size = md.len();
467 if size % EROFS_BLOCK_SIZE_4096 != 0 || (size >> EROFS_BLOCK_BITS_12) > u32::MAX as u64 {
468 return Err(einval!(format!(
469 "blob_cache: metadata blob size (0x{:x}) is invalid",
470 size
471 )));
472 }
473
474 Ok(MetaBlob {
475 file: File::from_std(file),
476 size,
477 })
478 }
479
480 pub fn blocks(&self) -> u32 {
482 (self.size >> EROFS_BLOCK_BITS_12) as u32
483 }
484
485 pub async fn async_read<T: IoBufMut>(&self, pos: u64, buf: T) -> (Result<usize>, T) {
487 self.file.read_at(buf, pos).await
488 }
489}
490
491pub struct DataBlob {
493 blob_id: String,
494 blob: Arc<dyn BlobCache>,
495 file: File,
496}
497
498impl DataBlob {
499 pub fn new(config: &Arc<DataBlobConfig>) -> Result<Self> {
501 let blob_id = config.blob_info().blob_id();
502 let blob = BLOB_FACTORY
503 .new_blob_cache(config.config_v2(), &config.blob_info)
504 .inspect_err(|_e| {
505 warn!(
506 "blob_cache: failed to create cache object for blob {}",
507 blob_id
508 );
509 })?;
510
511 match blob.get_blob_object() {
512 Some(obj) => {
513 let fd = nix::unistd::dup(obj.as_raw_fd())?;
514 let file = unsafe { File::from_raw_fd(fd) };
516 Ok(DataBlob {
517 blob_id,
518 blob,
519 file,
520 })
521 }
522 None => Err(eio!(format!(
523 "blob_cache: failed to get BlobObject for blob {}",
524 blob_id
525 ))),
526 }
527 }
528
529 pub async fn async_read<T: IoBufMut>(&self, pos: u64, buf: T) -> (Result<usize>, T) {
531 match self.blob.get_blob_object() {
532 Some(obj) => match obj.fetch_range_uncompressed(pos, buf.bytes_total() as u64) {
533 Ok(_) => self.file.read_at(buf, pos).await,
534 Err(e) => (Err(e), buf),
535 },
536 None => (
537 Err(eio!(format!(
538 "blob_cache: failed to get BlobObject for blob {}",
539 self.blob_id
540 ))),
541 buf,
542 ),
543 }
544 }
545}
546
547#[cfg(test)]
548mod tests {
549 use super::*;
550 use vmm_sys_util::tempdir::TempDir;
551
552 fn create_factory_config() -> String {
553 let config = r#"
554 {
555 "type": "bootstrap",
556 "id": "bootstrap1",
557 "domain_id": "userid1",
558 "config": {
559 "id": "factory1",
560 "backend_type": "localfs",
561 "backend_config": {
562 "dir": "/tmp/nydus"
563 },
564 "cache_type": "fscache",
565 "cache_config": {
566 "work_dir": "/tmp/nydus"
567 },
568 "metadata_path": "/tmp/nydus/bootstrap1"
569 }
570 }"#;
571
572 config.to_string()
573 }
574
575 #[test]
576 fn test_generate_blob_key() {
577 assert_eq!(&generate_blob_key("", "blob1"), "blob1");
578 assert_eq!(&generate_blob_key("domain1", "blob1"), "domain1/blob1");
579 }
580
581 #[test]
582 fn test_blob_cache_entry() {
583 let tmpdir = TempDir::new().unwrap();
584 let path = tmpdir.as_path().join("bootstrap1");
585 std::fs::write(path, "metadata").unwrap();
586 let cfg = create_factory_config();
587 let content = cfg.replace("/tmp/nydus", tmpdir.as_path().to_str().unwrap());
588 let mut entry: BlobCacheEntry = serde_json::from_str(&content).unwrap();
589 assert!(entry.prepare_configuration_info());
590 let blob_config = entry.blob_config.as_ref().unwrap();
591
592 assert_eq!(&entry.blob_type, "bootstrap");
593 assert_eq!(&entry.blob_id, "bootstrap1");
594 assert_eq!(&entry.domain_id, "userid1");
595 assert_eq!(&blob_config.id, "factory1");
596 assert_eq!(&blob_config.backend.backend_type, "localfs");
597 assert_eq!(&blob_config.cache.cache_type, "fscache");
598 assert!(blob_config.metadata_path.is_some());
599 assert!(blob_config.backend.localfs.is_some());
600 assert!(blob_config.cache.fs_cache.is_some());
601
602 let mgr = BlobCacheMgr::new();
603 let (path, config) = mgr.get_meta_info(&entry).unwrap();
604 let backend_cfg = config.get_backend_config().unwrap();
605 let cache_cfg = config.get_cache_config().unwrap();
606 assert_eq!(path, tmpdir.as_path().join("bootstrap1"));
607 assert_eq!(&config.id, "factory1");
608 assert_eq!(&backend_cfg.backend_type, "localfs");
609 assert_eq!(&cache_cfg.cache_type, "fscache");
610
611 let blob = MetaBlobConfig {
612 blob_id: "123456789-123".to_string(),
613 scoped_blob_id: "domain1".to_string(),
614 path: path.clone(),
615 config,
616 blobs: Mutex::new(Vec::new()),
617 blob_extra_infos: HashMap::new(),
618 is_tarfs_mode: false,
619 };
620 assert_eq!(blob.path(), &path);
621 assert_eq!(blob.blob_id(), "123456789-123");
622 }
623
624 #[test]
625 fn test_invalid_blob_id() {
626 let tmpdir = TempDir::new().unwrap();
627 let path = tmpdir.as_path().join("bootstrap1");
628 std::fs::write(path, "metadata").unwrap();
629 let config = create_factory_config();
630 let content = config.replace("/tmp/nydus", tmpdir.as_path().to_str().unwrap());
631 let mut entry: BlobCacheEntry = serde_json::from_str(&content).unwrap();
632 let mgr = BlobCacheMgr::new();
633
634 entry.blob_id = "domain2/blob1".to_string();
635 mgr.get_meta_info(&entry).unwrap_err();
636 }
637
638 #[test]
639 fn test_blob_cache_list() {
640 let config = r#"
641 {
642 "blobs" : [
643 {
644 "type": "bootstrap",
645 "id": "bootstrap1",
646 "domain_id": "userid1",
647 "config": {
648 "id": "factory1",
649 "backend_type": "localfs",
650 "backend_config": {
651 "dir": "/tmp/nydus"
652 },
653 "cache_type": "fscache",
654 "cache_config": {
655 "work_dir": "/tmp/nydus"
656 },
657 "metadata_path": "/tmp/nydus/bootstrap1"
658 }
659 },
660 {
661 "type": "bootstrap",
662 "id": "bootstrap2",
663 "domain_id": "userid2",
664 "config": {
665 "id": "factory1",
666 "backend_type": "localfs",
667 "backend_config": {
668 "dir": "/tmp/nydus"
669 },
670 "cache_type": "fscache",
671 "cache_config": {
672 "work_dir": "/tmp/nydus"
673 },
674 "metadata_path": "/tmp/nydus/bootstrap2"
675 }
676 }
677 ]
678 }"#;
679 let mut list: BlobCacheList = serde_json::from_str(config).unwrap();
680 assert!(list.blobs[0].prepare_configuration_info());
681
682 assert_eq!(list.blobs.len(), 2);
683 assert_eq!(&list.blobs[0].blob_type, "bootstrap");
684 assert_eq!(&list.blobs[0].blob_id, "bootstrap1");
685 let blob_config = &list.blobs[0].blob_config.as_ref().unwrap();
686 assert_eq!(&blob_config.id, "factory1");
687 assert_eq!(&blob_config.backend.backend_type, "localfs");
688 assert_eq!(&blob_config.cache.cache_type, "fscache");
689 assert_eq!(&list.blobs[1].blob_type, "bootstrap");
690 assert_eq!(&list.blobs[1].blob_id, "bootstrap2");
691 }
692
693 #[test]
694 fn test_add_bootstrap() {
695 let tmpdir = TempDir::new().unwrap();
696 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
697 let mut source_path = PathBuf::from(root_dir);
698 source_path.push("../tests/texture/bootstrap/rafs-v6-2.2.boot");
699
700 let config = r#"
701 {
702 "type": "bootstrap",
703 "id": "rafs-v6",
704 "domain_id": "domain2",
705 "config_v2": {
706 "version": 2,
707 "id": "factory1",
708 "backend": {
709 "type": "localfs",
710 "localfs": {
711 "dir": "/tmp/nydus"
712 }
713 },
714 "cache": {
715 "type": "fscache",
716 "fscache": {
717 "work_dir": "/tmp/nydus"
718 }
719 },
720 "metadata_path": "RAFS_V5"
721 }
722 }"#;
723 let content = config
724 .replace("/tmp/nydus", tmpdir.as_path().to_str().unwrap())
725 .replace("RAFS_V5", &source_path.display().to_string());
726 let mut entry: BlobCacheEntry = serde_json::from_str(&content).unwrap();
727 assert!(entry.prepare_configuration_info());
728
729 let mgr = BlobCacheMgr::new();
730 mgr.add_blob_entry(&entry).unwrap();
731 let blob_id = generate_blob_key(&entry.domain_id, &entry.blob_id);
732 assert!(mgr.get_config(&blob_id).is_some());
733
734 assert!(mgr.add_blob_entry(&entry).is_err());
736
737 let key = generate_blob_key(
739 &entry.domain_id,
740 "be7d77eeb719f70884758d1aa800ed0fb09d701aaec469964e9d54325f0d5fef",
741 );
742 assert!(mgr.get_config(&key).is_some());
743
744 assert_eq!(mgr.get_state().id_to_config_map.len(), 2);
745
746 entry.blob_id = "rafs-v6-cloned".to_string();
747 let blob_id_cloned = generate_blob_key(&entry.domain_id, &entry.blob_id);
748 mgr.add_blob_entry(&entry).unwrap();
749 assert_eq!(mgr.get_state().id_to_config_map.len(), 3);
750 assert!(mgr.get_config(&blob_id).is_some());
751 assert!(mgr.get_config(&blob_id_cloned).is_some());
752
753 mgr.remove_blob_entry(&BlobCacheObjectId {
754 domain_id: entry.domain_id.clone(),
755 blob_id: "rafs-v6".to_string(),
756 })
757 .unwrap();
758 assert_eq!(mgr.get_state().id_to_config_map.len(), 2);
759 assert!(mgr.get_config(&blob_id).is_none());
760 assert!(mgr.get_config(&blob_id_cloned).is_some());
761
762 mgr.remove_blob_entry(&BlobCacheObjectId {
763 domain_id: entry.domain_id,
764 blob_id: "rafs-v6-cloned".to_string(),
765 })
766 .unwrap();
767 assert_eq!(mgr.get_state().id_to_config_map.len(), 0);
768 assert!(mgr.get_config(&blob_id).is_none());
769 assert!(mgr.get_config(&blob_id_cloned).is_none());
770 }
771
772 #[test]
773 fn test_meta_blob() {
774 let root_dir = &std::env::var("CARGO_MANIFEST_DIR").expect("$CARGO_MANIFEST_DIR");
775 let mut source_path = PathBuf::from(root_dir);
776 source_path.push("../tests/texture/bootstrap/rafs-v6-2.2.boot");
777
778 tokio_uring::start(async move {
779 let meta_blob = MetaBlob::new(&source_path).unwrap();
780 assert_eq!(meta_blob.blocks(), 5);
781 let buf = vec![0u8; 4096];
782 let (res, buf) = meta_blob.async_read(0, buf).await;
783 assert_eq!(res.unwrap(), 4096);
784 assert_eq!(buf[0], 0);
785 assert_eq!(buf[1023], 0);
786 assert_eq!(buf[1024], 0xe2);
787 assert_eq!(buf[1027], 0xe0);
788 let (res, _buf) = meta_blob.async_read(0x6000, buf).await;
789 assert_eq!(res.unwrap(), 0);
790 });
791 }
792}