1use std::collections::HashMap;
7use std::convert::{TryFrom, TryInto};
8use std::fs;
9use std::io::{Error, ErrorKind, Result};
10use std::path::Path;
11use std::str::FromStr;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::Arc;
14
15use serde::Deserialize;
16use serde_json::Value;
17
18#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
20pub struct ConfigV2 {
21 pub version: u32,
23 #[serde(default)]
25 pub id: String,
26 pub backend: Option<BackendConfigV2>,
28 #[serde(default)]
30 pub external_backends: Vec<ExternalBackendConfig>,
31 pub cache: Option<CacheConfigV2>,
33 pub rafs: Option<RafsConfigV2>,
35 pub overlay: Option<OverlayConfig>,
37 #[serde(skip)]
39 pub internal: ConfigV2Internal,
40}
41
42impl Default for ConfigV2 {
43 fn default() -> Self {
44 ConfigV2 {
45 version: 2,
46 id: String::new(),
47 backend: None,
48 external_backends: Vec::new(),
49 cache: None,
50 rafs: None,
51 overlay: None,
52 internal: ConfigV2Internal::default(),
53 }
54 }
55}
56
57impl ConfigV2 {
58 pub fn new(id: &str) -> Self {
60 ConfigV2 {
61 version: 2,
62 id: id.to_string(),
63 backend: None,
64 external_backends: Vec::new(),
65 cache: None,
66 rafs: None,
67 overlay: None,
68 internal: ConfigV2Internal::default(),
69 }
70 }
71
72 pub fn new_localfs(id: &str, dir: &str) -> Result<Self> {
74 let content = format!(
75 r#"
76 version = 2
77 id = "{}"
78 backend.type = "localfs"
79 backend.localfs.dir = "{}"
80 cache.type = "filecache"
81 cache.compressed = false
82 cache.validate = false
83 cache.filecache.work_dir = "{}"
84 "#,
85 id, dir, dir
86 );
87
88 Self::from_str(&content)
89 }
90
91 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
93 let md = fs::metadata(path.as_ref())?;
94 if md.len() > 0x100000 {
95 return Err(Error::new(
96 ErrorKind::Other,
97 "configuration file size is too big",
98 ));
99 }
100 let content = fs::read_to_string(path)?;
101 Self::from_str(&content)
102 }
103
104 pub fn validate(&self) -> bool {
106 if self.version != 2 {
107 return false;
108 }
109 if let Some(backend_cfg) = self.backend.as_ref() {
110 if !backend_cfg.validate() {
111 return false;
112 }
113 }
114 if let Some(cache_cfg) = self.cache.as_ref() {
115 if !cache_cfg.validate() {
116 return false;
117 }
118 }
119 if let Some(rafs_cfg) = self.rafs.as_ref() {
120 if !rafs_cfg.validate() {
121 return false;
122 }
123 }
124
125 true
126 }
127
128 pub fn get_backend_config(&self) -> Result<&BackendConfigV2> {
130 self.backend.as_ref().ok_or_else(|| {
131 Error::new(
132 ErrorKind::InvalidInput,
133 "no configuration information for backend",
134 )
135 })
136 }
137
138 pub fn get_cache_config(&self) -> Result<&CacheConfigV2> {
140 self.cache.as_ref().ok_or_else(|| {
141 Error::new(
142 ErrorKind::InvalidData,
143 "no configuration information for cache",
144 )
145 })
146 }
147
148 pub fn get_cache_working_directory(&self) -> Result<String> {
150 let cache = self.get_cache_config()?;
151 if cache.is_filecache() {
152 if let Some(c) = cache.file_cache.as_ref() {
153 return Ok(c.work_dir.clone());
154 }
155 } else if cache.is_fscache() {
156 if let Some(c) = cache.fs_cache.as_ref() {
157 return Ok(c.work_dir.clone());
158 }
159 }
160
161 Err(Error::new(
162 ErrorKind::NotFound,
163 "no working directory configured",
164 ))
165 }
166
167 pub fn get_rafs_config(&self) -> Result<&RafsConfigV2> {
169 self.rafs.as_ref().ok_or_else(|| {
170 Error::new(
171 ErrorKind::InvalidInput,
172 "no configuration information for rafs",
173 )
174 })
175 }
176
177 pub fn clone_without_secrets(&self) -> Self {
179 let mut cfg = self.clone();
180
181 if let Some(backend_cfg) = cfg.backend.as_mut() {
182 if let Some(oss_cfg) = backend_cfg.oss.as_mut() {
183 oss_cfg.access_key_id = String::new();
184 oss_cfg.access_key_secret = String::new();
185 }
186 if let Some(registry_cfg) = backend_cfg.registry.as_mut() {
187 registry_cfg.auth = None;
188 registry_cfg.registry_token = None;
189 }
190 }
191
192 cfg
193 }
194
195 pub fn is_chunk_validation_enabled(&self) -> bool {
197 let mut validation = if let Some(cache) = &self.cache {
198 cache.cache_validate
199 } else {
200 false
201 };
202 if let Some(rafs) = &self.rafs {
203 if rafs.validate {
204 validation = true;
205 }
206 }
207
208 validation
209 }
210
211 pub fn is_fs_cache(&self) -> bool {
213 if let Some(cache) = self.cache.as_ref() {
214 cache.fs_cache.is_some()
215 } else {
216 false
217 }
218 }
219
220 pub fn update_registry_auth_info(&mut self, auth: &Option<String>) {
222 if let Some(auth) = auth {
223 if let Some(backend) = self.backend.as_mut() {
224 if let Some(registry) = backend.registry.as_mut() {
225 registry.auth = Some(auth.to_string());
226 }
227 }
228 }
229 }
230}
231
232impl FromStr for ConfigV2 {
233 type Err = std::io::Error;
234
235 fn from_str(s: &str) -> Result<ConfigV2> {
236 if let Ok(v) = serde_json::from_str::<ConfigV2>(s) {
237 return if v.validate() {
238 Ok(v)
239 } else {
240 Err(Error::new(ErrorKind::InvalidInput, "invalid configuration"))
241 };
242 }
243 if let Ok(v) = toml::from_str::<ConfigV2>(s) {
244 return if v.validate() {
245 Ok(v)
246 } else {
247 Err(Error::new(ErrorKind::InvalidInput, "invalid configuration"))
248 };
249 }
250 if let Ok(v) = serde_json::from_str::<RafsConfig>(s) {
251 if let Ok(v) = ConfigV2::try_from(v) {
252 if v.validate() {
253 return Ok(v);
254 }
255 }
256 }
257 Err(Error::new(
258 ErrorKind::InvalidInput,
259 "failed to parse configuration information",
260 ))
261 }
262}
263
264#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
266pub struct BackendConfigV2 {
267 #[serde(rename = "type")]
269 pub backend_type: String,
270 pub localdisk: Option<LocalDiskConfig>,
272 pub localfs: Option<LocalFsConfig>,
274 pub oss: Option<OssConfig>,
276 pub s3: Option<S3Config>,
278 pub registry: Option<RegistryConfig>,
280 #[serde(rename = "http-proxy")]
282 pub http_proxy: Option<HttpProxyConfig>,
283}
284
285impl BackendConfigV2 {
286 pub fn validate(&self) -> bool {
288 match self.backend_type.as_str() {
289 "localdisk" => match self.localdisk.as_ref() {
290 Some(v) => {
291 if v.device_path.is_empty() {
292 return false;
293 }
294 }
295 None => return false,
296 },
297 "localfs" => match self.localfs.as_ref() {
298 Some(v) => {
299 if v.blob_file.is_empty() && v.dir.is_empty() {
300 return false;
301 }
302 }
303 None => return false,
304 },
305 "oss" => match self.oss.as_ref() {
306 Some(v) => {
307 if v.endpoint.is_empty() || v.bucket_name.is_empty() {
308 return false;
309 }
310 }
311 None => return false,
312 },
313 "s3" => match self.s3.as_ref() {
314 Some(v) => {
315 if v.region.is_empty() || v.bucket_name.is_empty() {
316 return false;
317 }
318 }
319 None => return false,
320 },
321 "registry" => match self.registry.as_ref() {
322 Some(v) => {
323 if v.host.is_empty() || v.repo.is_empty() {
324 return false;
325 }
326 }
327 None => return false,
328 },
329
330 "http-proxy" => match self.http_proxy.as_ref() {
331 Some(v) => {
332 let is_valid_unix_socket_path = |path: &str| {
333 let path = Path::new(path);
334 path.is_absolute() && path.exists()
335 };
336 if v.addr.is_empty()
337 || !(v.addr.starts_with("http://")
338 || v.addr.starts_with("https://")
339 || is_valid_unix_socket_path(&v.addr))
340 {
341 return false;
342 }
343
344 if Path::new(&v.path).join("any_blob_id").to_str().is_none() {
346 return false;
347 }
348 }
349 None => return false,
350 },
351 _ => return false,
352 }
353
354 true
355 }
356
357 pub fn get_localdisk_config(&self) -> Result<&LocalDiskConfig> {
359 if &self.backend_type != "localdisk" {
360 Err(Error::new(
361 ErrorKind::InvalidInput,
362 "backend type is not 'localdisk'",
363 ))
364 } else {
365 self.localdisk.as_ref().ok_or_else(|| {
366 Error::new(
367 ErrorKind::InvalidData,
368 "no configuration information for localdisk",
369 )
370 })
371 }
372 }
373
374 pub fn get_localfs_config(&self) -> Result<&LocalFsConfig> {
376 if &self.backend_type != "localfs" {
377 Err(Error::new(
378 ErrorKind::InvalidInput,
379 "backend type is not 'localfs'",
380 ))
381 } else {
382 self.localfs.as_ref().ok_or_else(|| {
383 Error::new(
384 ErrorKind::InvalidData,
385 "no configuration information for localfs",
386 )
387 })
388 }
389 }
390
391 pub fn get_oss_config(&self) -> Result<&OssConfig> {
393 if &self.backend_type != "oss" {
394 Err(Error::new(
395 ErrorKind::InvalidInput,
396 "backend type is not 'oss'",
397 ))
398 } else {
399 self.oss.as_ref().ok_or_else(|| {
400 Error::new(
401 ErrorKind::InvalidData,
402 "no configuration information for OSS",
403 )
404 })
405 }
406 }
407
408 pub fn get_s3_config(&self) -> Result<&S3Config> {
410 if &self.backend_type != "s3" {
411 Err(Error::new(
412 ErrorKind::InvalidInput,
413 "backend type is not 's3'",
414 ))
415 } else {
416 self.s3.as_ref().ok_or_else(|| {
417 Error::new(
418 ErrorKind::InvalidData,
419 "no configuration information for s3",
420 )
421 })
422 }
423 }
424
425 pub fn get_registry_config(&self) -> Result<&RegistryConfig> {
427 if &self.backend_type != "registry" {
428 Err(Error::new(
429 ErrorKind::InvalidInput,
430 "backend type is not 'registry'",
431 ))
432 } else {
433 self.registry.as_ref().ok_or_else(|| {
434 Error::new(
435 ErrorKind::InvalidData,
436 "no configuration information for registry",
437 )
438 })
439 }
440 }
441
442 pub fn get_http_proxy_config(&self) -> Result<&HttpProxyConfig> {
444 if &self.backend_type != "http-proxy" {
445 Err(Error::new(
446 ErrorKind::InvalidInput,
447 "backend type is not 'http-proxy'",
448 ))
449 } else {
450 self.http_proxy.as_ref().ok_or_else(|| {
451 Error::new(
452 ErrorKind::InvalidData,
453 "no configuration information for http-proxy",
454 )
455 })
456 }
457 }
458}
459
460#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
462pub struct LocalDiskConfig {
463 #[serde(default)]
465 pub device_path: String,
466 #[serde(default)]
468 pub disable_gpt: bool,
469}
470
471#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
473pub struct LocalFsConfig {
474 #[serde(default)]
476 pub blob_file: String,
477 #[serde(default)]
479 pub dir: String,
480 #[serde(default)]
482 pub alt_dirs: Vec<String>,
483}
484
485#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
487pub struct OssConfig {
488 #[serde(default = "default_http_scheme")]
490 pub scheme: String,
491 pub endpoint: String,
493 pub bucket_name: String,
495 #[serde(default)]
500 pub object_prefix: String,
501 #[serde(default)]
503 pub access_key_id: String,
504 #[serde(default)]
506 pub access_key_secret: String,
507 #[serde(default)]
509 pub skip_verify: bool,
510 #[serde(default = "default_http_timeout")]
512 pub timeout: u32,
513 #[serde(default = "default_http_timeout")]
515 pub connect_timeout: u32,
516 #[serde(default)]
518 pub retry_limit: u8,
519 #[serde(default)]
521 pub proxy: ProxyConfig,
522 #[serde(default)]
524 pub mirrors: Vec<MirrorConfig>,
525}
526
527#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
529pub struct S3Config {
530 #[serde(default = "default_http_scheme")]
532 pub scheme: String,
533 pub endpoint: String,
535 pub region: String,
537 pub bucket_name: String,
539 #[serde(default)]
544 pub object_prefix: String,
545 #[serde(default)]
547 pub access_key_id: String,
548 #[serde(default)]
550 pub access_key_secret: String,
551 #[serde(default)]
553 pub skip_verify: bool,
554 #[serde(default = "default_http_timeout")]
556 pub timeout: u32,
557 #[serde(default = "default_http_timeout")]
559 pub connect_timeout: u32,
560 #[serde(default)]
562 pub retry_limit: u8,
563 #[serde(default)]
565 pub proxy: ProxyConfig,
566 #[serde(default)]
568 pub mirrors: Vec<MirrorConfig>,
569}
570
571#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
573pub struct HttpProxyConfig {
574 pub addr: String,
576 #[serde(default)]
579 pub path: String,
580 #[serde(default)]
582 pub skip_verify: bool,
583 #[serde(default = "default_http_timeout")]
585 pub timeout: u32,
586 #[serde(default = "default_http_timeout")]
588 pub connect_timeout: u32,
589 #[serde(default)]
591 pub retry_limit: u8,
592 #[serde(default)]
594 pub proxy: ProxyConfig,
595 #[serde(default)]
597 pub mirrors: Vec<MirrorConfig>,
598}
599
600#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
602pub struct RegistryConfig {
603 #[serde(default = "default_http_scheme")]
605 pub scheme: String,
606 pub host: String,
608 pub repo: String,
610 #[serde(default)]
612 pub auth: Option<String>,
613 #[serde(default)]
615 pub skip_verify: bool,
616 #[serde(default = "default_http_timeout")]
618 pub timeout: u32,
619 #[serde(default = "default_http_timeout")]
621 pub connect_timeout: u32,
622 #[serde(default)]
624 pub retry_limit: u8,
625 #[serde(default)]
627 pub registry_token: Option<String>,
628 #[serde(default)]
631 pub blob_url_scheme: String,
632 #[serde(default)]
634 pub blob_redirected_host: String,
635 #[serde(default)]
637 pub proxy: ProxyConfig,
638 #[serde(default)]
640 pub mirrors: Vec<MirrorConfig>,
641}
642
643#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
645pub struct CacheConfigV2 {
646 #[serde(default, rename = "type")]
648 pub cache_type: String,
649 #[serde(default, rename = "compressed")]
651 pub cache_compressed: bool,
652 #[serde(default, rename = "validate")]
654 pub cache_validate: bool,
655 #[serde(default)]
657 pub prefetch: PrefetchConfigV2,
658 #[serde(rename = "filecache")]
660 pub file_cache: Option<FileCacheConfig>,
661 #[serde(rename = "fscache")]
662 pub fs_cache: Option<FsCacheConfig>,
664}
665
666impl CacheConfigV2 {
667 pub fn validate(&self) -> bool {
669 match self.cache_type.as_str() {
670 "blobcache" | "filecache" => {
671 if let Some(c) = self.file_cache.as_ref() {
672 if c.work_dir.is_empty() {
673 return false;
674 }
675 } else {
676 return false;
677 }
678 }
679 "fscache" => {
680 if let Some(c) = self.fs_cache.as_ref() {
681 if c.work_dir.is_empty() {
682 return false;
683 }
684 } else {
685 return false;
686 }
687 }
688 "" | "dummycache" => {}
689 _ => return false,
690 }
691
692 if self.prefetch.enable {
693 if self.prefetch.batch_size > 0x10000000 {
694 return false;
695 }
696 if self.prefetch.threads_count == 0 || self.prefetch.threads_count > 1024 {
697 return false;
698 }
699 }
700
701 true
702 }
703
704 pub fn is_filecache(&self) -> bool {
706 self.cache_type == "blobcache" || self.cache_type == "filecache"
707 }
708
709 pub fn is_fscache(&self) -> bool {
711 self.cache_type == "fscache"
712 }
713
714 pub fn get_filecache_config(&self) -> Result<&FileCacheConfig> {
716 if self.is_filecache() {
717 self.file_cache.as_ref().ok_or_else(|| {
718 Error::new(
719 ErrorKind::InvalidInput,
720 "no configuration information for filecache",
721 )
722 })
723 } else {
724 Err(Error::new(
725 ErrorKind::InvalidData,
726 "cache type is not 'filecache'",
727 ))
728 }
729 }
730
731 pub fn get_fscache_config(&self) -> Result<&FsCacheConfig> {
733 if self.is_fscache() {
734 self.fs_cache.as_ref().ok_or_else(|| {
735 Error::new(
736 ErrorKind::InvalidData,
737 "no configuration information for fscache",
738 )
739 })
740 } else {
741 Err(Error::new(
742 ErrorKind::InvalidInput,
743 "cache type is not 'fscache'",
744 ))
745 }
746 }
747}
748
749#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
751pub struct FileCacheConfig {
752 #[serde(default = "default_work_dir")]
754 pub work_dir: String,
755 #[serde(default)]
757 pub disable_indexed_map: bool,
758 #[serde(default)]
760 pub enable_encryption: bool,
761 #[serde(default)]
763 pub enable_convergent_encryption: bool,
764 #[serde(default)]
766 pub encryption_key: String,
767}
768
769impl FileCacheConfig {
770 pub fn get_work_dir(&self) -> Result<&str> {
772 let path = fs::metadata(&self.work_dir)
773 .or_else(|_| {
774 fs::create_dir_all(&self.work_dir)?;
775 fs::metadata(&self.work_dir)
776 })
777 .map_err(|e| {
778 log::error!("fail to stat filecache work_dir {}: {}", self.work_dir, e);
779 e
780 })?;
781
782 if path.is_dir() {
783 Ok(&self.work_dir)
784 } else {
785 Err(Error::new(
786 ErrorKind::NotFound,
787 format!("filecache work_dir {} is not a directory", self.work_dir),
788 ))
789 }
790 }
791}
792
793#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
795pub struct FsCacheConfig {
796 #[serde(default = "default_work_dir")]
798 pub work_dir: String,
799}
800
801impl FsCacheConfig {
802 pub fn get_work_dir(&self) -> Result<&str> {
804 let path = fs::metadata(&self.work_dir)
805 .or_else(|_| {
806 fs::create_dir_all(&self.work_dir)?;
807 fs::metadata(&self.work_dir)
808 })
809 .map_err(|e| {
810 log::error!("fail to stat fscache work_dir {}: {}", self.work_dir, e);
811 e
812 })?;
813
814 if path.is_dir() {
815 Ok(&self.work_dir)
816 } else {
817 Err(Error::new(
818 ErrorKind::NotFound,
819 format!("fscache work_dir {} is not a directory", self.work_dir),
820 ))
821 }
822 }
823}
824
825#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
827pub struct RafsConfigV2 {
828 #[serde(default = "default_rafs_mode")]
830 pub mode: String,
831 #[serde(rename = "batch_size", default = "default_user_io_batch_size")]
833 pub user_io_batch_size: usize,
834 #[serde(default)]
836 pub validate: bool,
837 #[serde(default)]
839 pub enable_xattr: bool,
840 #[serde(default)]
844 pub iostats_files: bool,
845 #[serde(default)]
847 pub access_pattern: bool,
848 #[serde(default)]
850 pub latest_read_files: bool,
851 #[serde(default)]
853 pub prefetch: PrefetchConfigV2,
854}
855
856impl RafsConfigV2 {
857 pub fn validate(&self) -> bool {
859 if self.mode != "direct" && self.mode != "cached" {
860 return false;
861 }
862 if self.user_io_batch_size > 0x10000000 {
863 return false;
864 }
865 if self.prefetch.enable {
866 if self.prefetch.batch_size > 0x10000000 {
867 return false;
868 }
869 if self.prefetch.threads_count == 0 || self.prefetch.threads_count > 1024 {
870 return false;
871 }
872 }
873
874 true
875 }
876}
877
878#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
880pub struct PrefetchConfigV2 {
881 pub enable: bool,
883 #[serde(rename = "threads", default = "default_prefetch_threads_count")]
885 pub threads_count: usize,
886 #[serde(default = "default_prefetch_batch_size")]
888 pub batch_size: usize,
889 #[serde(default)]
891 pub bandwidth_limit: u32,
892 #[serde(default)]
894 pub prefetch_all: bool,
895}
896
897#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
899pub struct ProxyConfig {
900 #[serde(default)]
902 pub url: String,
903 #[serde(default)]
905 pub ping_url: String,
906 #[serde(default = "default_true")]
908 pub fallback: bool,
909 #[serde(default = "default_check_interval")]
911 pub check_interval: u64,
912 #[serde(default)]
914 pub use_http: bool,
915 #[serde(default = "default_check_pause_elapsed")]
917 pub check_pause_elapsed: u64,
918}
919
920impl Default for ProxyConfig {
921 fn default() -> Self {
922 Self {
923 url: String::new(),
924 ping_url: String::new(),
925 fallback: true,
926 check_interval: 5,
927 use_http: false,
928 check_pause_elapsed: 300,
929 }
930 }
931}
932
933#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
935pub struct MirrorConfig {
936 pub host: String,
938 #[serde(default)]
940 pub ping_url: String,
941 #[serde(default)]
943 pub headers: HashMap<String, String>,
944 #[serde(default = "default_check_interval")]
946 pub health_check_interval: u64,
947 #[serde(default = "default_failure_limit")]
949 pub failure_limit: u8,
950 #[serde(default = "default_check_pause_elapsed")]
952 pub health_check_pause_elapsed: u64,
953}
954
955impl Default for MirrorConfig {
956 fn default() -> Self {
957 Self {
958 host: String::new(),
959 headers: HashMap::new(),
960 health_check_interval: 5,
961 failure_limit: 5,
962 ping_url: String::new(),
963 health_check_pause_elapsed: 300,
964 }
965 }
966}
967
968#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
970pub struct BlobCacheEntryConfigV2 {
971 pub version: u32,
973 #[serde(default)]
975 pub id: String,
976 #[serde(default)]
978 pub backend: BackendConfigV2,
979 #[serde(default)]
981 pub external_backends: Vec<ExternalBackendConfig>,
982 #[serde(default)]
984 pub cache: CacheConfigV2,
985 #[serde(default)]
987 pub metadata_path: Option<String>,
988}
989
990impl BlobCacheEntryConfigV2 {
991 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
993 let md = fs::metadata(path.as_ref())?;
994 if md.len() > 0x100000 {
995 return Err(Error::new(
996 ErrorKind::InvalidInput,
997 "configuration file size is too big",
998 ));
999 }
1000 let content = fs::read_to_string(path)?;
1001 Self::from_str(&content)
1002 }
1003
1004 pub fn validate(&self) -> bool {
1006 if self.version != 2 {
1007 return false;
1008 }
1009 let config: ConfigV2 = self.into();
1010 config.validate()
1011 }
1012}
1013
1014impl FromStr for BlobCacheEntryConfigV2 {
1015 type Err = Error;
1016
1017 fn from_str(s: &str) -> Result<BlobCacheEntryConfigV2> {
1018 if let Ok(v) = serde_json::from_str::<BlobCacheEntryConfigV2>(s) {
1019 return if v.validate() {
1020 Ok(v)
1021 } else {
1022 Err(Error::new(ErrorKind::InvalidInput, "invalid configuration"))
1023 };
1024 }
1025 if let Ok(v) = toml::from_str::<BlobCacheEntryConfigV2>(s) {
1026 return if v.validate() {
1027 Ok(v)
1028 } else {
1029 Err(Error::new(ErrorKind::InvalidInput, "invalid configuration"))
1030 };
1031 }
1032 Err(Error::new(
1033 ErrorKind::InvalidInput,
1034 "failed to parse configuration information",
1035 ))
1036 }
1037}
1038
1039impl From<&BlobCacheEntryConfigV2> for ConfigV2 {
1040 fn from(c: &BlobCacheEntryConfigV2) -> Self {
1041 ConfigV2 {
1042 version: c.version,
1043 id: c.id.clone(),
1044 backend: Some(c.backend.clone()),
1045 external_backends: c.external_backends.clone(),
1046 cache: Some(c.cache.clone()),
1047 rafs: None,
1048 overlay: None,
1049 internal: ConfigV2Internal::default(),
1050 }
1051 }
1052}
1053
1054#[derive(Clone, Debug)]
1056pub struct ConfigV2Internal {
1057 pub blob_accessible: Arc<AtomicBool>,
1059}
1060
1061impl Default for ConfigV2Internal {
1062 fn default() -> Self {
1063 ConfigV2Internal {
1064 blob_accessible: Arc::new(AtomicBool::new(false)),
1065 }
1066 }
1067}
1068
1069impl PartialEq for ConfigV2Internal {
1070 fn eq(&self, other: &Self) -> bool {
1071 self.blob_accessible() == other.blob_accessible()
1072 }
1073}
1074
1075impl Eq for ConfigV2Internal {}
1076
1077impl ConfigV2Internal {
1078 pub fn blob_accessible(&self) -> bool {
1080 self.blob_accessible.load(Ordering::Relaxed)
1081 }
1082
1083 pub fn set_blob_accessible(&self, accessible: bool) {
1085 self.blob_accessible.store(accessible, Ordering::Relaxed);
1086 }
1087}
1088
1089pub const BLOB_CACHE_TYPE_META_BLOB: &str = "bootstrap";
1091pub const BLOB_CACHE_TYPE_DATA_BLOB: &str = "datablob";
1093
1094#[derive(Debug, Deserialize, Serialize, Clone)]
1096pub struct BlobCacheEntry {
1097 #[serde(rename = "type")]
1099 pub blob_type: String,
1100 #[serde(rename = "id")]
1102 pub blob_id: String,
1103 #[serde(default, rename = "config")]
1105 pub(crate) blob_config_legacy: Option<BlobCacheEntryConfig>,
1106 #[serde(default, rename = "config_v2")]
1108 pub blob_config: Option<BlobCacheEntryConfigV2>,
1109 #[serde(default)]
1111 pub domain_id: String,
1112}
1113
1114impl BlobCacheEntry {
1115 pub fn prepare_configuration_info(&mut self) -> bool {
1116 if self.blob_config.is_none() {
1117 if let Some(legacy) = self.blob_config_legacy.as_ref() {
1118 match legacy.try_into() {
1119 Err(_) => return false,
1120 Ok(v) => self.blob_config = Some(v),
1121 }
1122 }
1123 }
1124
1125 match self.blob_config.as_ref() {
1126 None => false,
1127 Some(cfg) => cfg.cache.validate() && cfg.backend.validate(),
1128 }
1129 }
1130}
1131
1132impl BlobCacheEntry {
1133 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
1135 let md = fs::metadata(path.as_ref())?;
1136 if md.len() > 0x100000 {
1137 return Err(Error::new(
1138 ErrorKind::InvalidInput,
1139 "configuration file size is too big",
1140 ));
1141 }
1142 let content = fs::read_to_string(path)?;
1143 Self::from_str(&content)
1144 }
1145
1146 pub fn validate(&self) -> bool {
1148 if self.blob_type != BLOB_CACHE_TYPE_META_BLOB
1149 && self.blob_type != BLOB_CACHE_TYPE_DATA_BLOB
1150 {
1151 log::warn!("invalid blob type {} for blob cache entry", self.blob_type);
1152 return false;
1153 }
1154 if let Some(config) = self.blob_config.as_ref() {
1155 if !config.validate() {
1156 return false;
1157 }
1158 }
1159 true
1160 }
1161}
1162
1163impl FromStr for BlobCacheEntry {
1164 type Err = Error;
1165
1166 fn from_str(s: &str) -> Result<BlobCacheEntry> {
1167 if let Ok(v) = serde_json::from_str::<BlobCacheEntry>(s) {
1168 return if v.validate() {
1169 Ok(v)
1170 } else {
1171 Err(Error::new(ErrorKind::InvalidInput, "invalid configuration"))
1172 };
1173 }
1174 if let Ok(v) = toml::from_str::<BlobCacheEntry>(s) {
1175 return if v.validate() {
1176 Ok(v)
1177 } else {
1178 Err(Error::new(ErrorKind::InvalidInput, "invalid configuration"))
1179 };
1180 }
1181 Err(Error::new(
1182 ErrorKind::InvalidInput,
1183 "failed to parse configuration information",
1184 ))
1185 }
1186}
1187
1188#[derive(Debug, Default, Deserialize, Serialize)]
1190pub struct BlobCacheList {
1191 pub blobs: Vec<BlobCacheEntry>,
1193}
1194
1195fn default_true() -> bool {
1196 true
1197}
1198
1199fn default_http_scheme() -> String {
1200 "https".to_string()
1201}
1202
1203fn default_http_timeout() -> u32 {
1204 5
1205}
1206
1207fn default_check_interval() -> u64 {
1208 5
1209}
1210
1211fn default_check_pause_elapsed() -> u64 {
1212 300
1213}
1214
1215fn default_failure_limit() -> u8 {
1216 5
1217}
1218
1219fn default_work_dir() -> String {
1220 ".".to_string()
1221}
1222
1223pub fn default_user_io_batch_size() -> usize {
1224 1024 * 1024
1225}
1226
1227pub fn default_prefetch_batch_size() -> usize {
1228 1024 * 1024
1229}
1230
1231fn default_prefetch_threads_count() -> usize {
1232 8
1233}
1234
1235fn default_prefetch_all() -> bool {
1236 true
1237}
1238
1239fn default_rafs_mode() -> String {
1240 "direct".to_string()
1241}
1242
1243#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1249struct BackendConfig {
1250 #[serde(rename = "type")]
1252 pub backend_type: String,
1253 #[serde(rename = "config")]
1256 pub backend_config: Value,
1257}
1258
1259impl TryFrom<&BackendConfig> for BackendConfigV2 {
1260 type Error = std::io::Error;
1261
1262 fn try_from(value: &BackendConfig) -> std::result::Result<Self, Self::Error> {
1263 let mut config = BackendConfigV2 {
1264 backend_type: value.backend_type.clone(),
1265 localdisk: None,
1266 localfs: None,
1267 oss: None,
1268 s3: None,
1269 registry: None,
1270 http_proxy: None,
1271 };
1272
1273 match value.backend_type.as_str() {
1274 "localdisk" => {
1275 config.localdisk = Some(serde_json::from_value(value.backend_config.clone())?);
1276 }
1277 "localfs" => {
1278 config.localfs = Some(serde_json::from_value(value.backend_config.clone())?);
1279 }
1280 "oss" => {
1281 config.oss = Some(serde_json::from_value(value.backend_config.clone())?);
1282 }
1283 "s3" => {
1284 config.s3 = Some(serde_json::from_value(value.backend_config.clone())?);
1285 }
1286 "registry" => {
1287 config.registry = Some(serde_json::from_value(value.backend_config.clone())?);
1288 }
1289 v => {
1290 return Err(Error::new(
1291 ErrorKind::InvalidInput,
1292 format!("unsupported backend type '{}'", v),
1293 ))
1294 }
1295 }
1296
1297 Ok(config)
1298 }
1299}
1300
1301#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1303struct CacheConfig {
1304 #[serde(default, rename = "type")]
1306 pub cache_type: String,
1307 #[serde(default, rename = "compressed")]
1309 pub cache_compressed: bool,
1310 #[serde(default, rename = "config")]
1312 pub cache_config: Value,
1313 #[serde(default, rename = "validate")]
1315 pub cache_validate: bool,
1316 #[serde(skip_serializing, skip_deserializing)]
1318 pub prefetch_config: BlobPrefetchConfig,
1319}
1320
1321#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1324pub struct ExternalBackendConfig {
1325 pub patch: HashMap<String, String>,
1327 #[serde(rename = "type")]
1329 pub kind: String,
1330 pub config: HashMap<String, String>,
1332}
1333
1334impl TryFrom<&CacheConfig> for CacheConfigV2 {
1335 type Error = std::io::Error;
1336
1337 fn try_from(v: &CacheConfig) -> std::result::Result<Self, Self::Error> {
1338 let mut config = CacheConfigV2 {
1339 cache_type: v.cache_type.clone(),
1340 cache_compressed: v.cache_compressed,
1341 cache_validate: v.cache_validate,
1342 prefetch: (&v.prefetch_config).into(),
1343 file_cache: None,
1344 fs_cache: None,
1345 };
1346
1347 match v.cache_type.as_str() {
1348 "blobcache" | "filecache" => {
1349 config.file_cache = Some(serde_json::from_value(v.cache_config.clone())?);
1350 }
1351 "fscache" => {
1352 config.fs_cache = Some(serde_json::from_value(v.cache_config.clone())?);
1353 }
1354 "" | "dummycache" => {}
1355 t => {
1356 return Err(Error::new(
1357 ErrorKind::InvalidInput,
1358 format!("unsupported cache type '{}'", t),
1359 ))
1360 }
1361 }
1362
1363 Ok(config)
1364 }
1365}
1366
1367#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1369struct FactoryConfig {
1370 #[serde(default)]
1372 pub id: String,
1373 pub backend: BackendConfig,
1375 #[serde(default)]
1377 pub external_backends: Vec<ExternalBackendConfig>,
1378 #[serde(default)]
1380 pub cache: CacheConfig,
1381}
1382
1383#[derive(Clone, Default, Deserialize)]
1385struct RafsConfig {
1386 pub device: FactoryConfig,
1388 pub mode: String,
1390 #[serde(default)]
1392 pub digest_validate: bool,
1393 #[serde(default)]
1395 pub iostats_files: bool,
1396 #[serde(default)]
1398 pub fs_prefetch: FsPrefetchControl,
1399 #[serde(default)]
1401 pub enable_xattr: bool,
1402 #[serde(default)]
1404 pub access_pattern: bool,
1405 #[serde(default)]
1407 pub latest_read_files: bool,
1408 #[serde(rename = "amplify_io", default = "default_user_io_batch_size")]
1411 pub user_io_batch_size: usize,
1412}
1413
1414impl TryFrom<RafsConfig> for ConfigV2 {
1415 type Error = std::io::Error;
1416
1417 fn try_from(v: RafsConfig) -> std::result::Result<Self, Self::Error> {
1418 let backend: BackendConfigV2 = (&v.device.backend).try_into()?;
1419 let mut cache: CacheConfigV2 = (&v.device.cache).try_into()?;
1420 let rafs = RafsConfigV2 {
1421 mode: v.mode,
1422 user_io_batch_size: v.user_io_batch_size,
1423 validate: v.digest_validate,
1424 enable_xattr: v.enable_xattr,
1425 iostats_files: v.iostats_files,
1426 access_pattern: v.access_pattern,
1427 latest_read_files: v.latest_read_files,
1428 prefetch: v.fs_prefetch.into(),
1429 };
1430 if !cache.prefetch.enable && rafs.prefetch.enable {
1431 cache.prefetch = rafs.prefetch.clone();
1432 }
1433
1434 Ok(ConfigV2 {
1435 version: 2,
1436 id: v.device.id,
1437 backend: Some(backend),
1438 external_backends: v.device.external_backends,
1439 cache: Some(cache),
1440 rafs: Some(rafs),
1441 overlay: None,
1442 internal: ConfigV2Internal::default(),
1443 })
1444 }
1445}
1446
1447#[derive(Clone, Default, Deserialize)]
1449struct FsPrefetchControl {
1450 #[serde(default)]
1452 pub enable: bool,
1453
1454 #[serde(default = "default_prefetch_threads_count")]
1456 pub threads_count: usize,
1457
1458 #[serde(rename = "merging_size", default = "default_prefetch_batch_size")]
1460 pub batch_size: usize,
1461
1462 #[serde(default, rename = "bandwidth_rate")]
1471 pub bandwidth_limit: u32,
1472
1473 #[serde(default = "default_prefetch_all")]
1475 pub prefetch_all: bool,
1476}
1477
1478impl From<FsPrefetchControl> for PrefetchConfigV2 {
1479 fn from(v: FsPrefetchControl) -> Self {
1480 PrefetchConfigV2 {
1481 enable: v.enable,
1482 threads_count: v.threads_count,
1483 batch_size: v.batch_size,
1484 bandwidth_limit: v.bandwidth_limit,
1485 prefetch_all: v.prefetch_all,
1486 }
1487 }
1488}
1489
1490#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
1492struct BlobPrefetchConfig {
1493 pub enable: bool,
1495 pub threads_count: usize,
1497 #[serde(rename = "merging_size")]
1499 pub batch_size: usize,
1500 #[serde(rename = "bandwidth_rate")]
1502 pub bandwidth_limit: u32,
1503}
1504
1505impl From<&BlobPrefetchConfig> for PrefetchConfigV2 {
1506 fn from(v: &BlobPrefetchConfig) -> Self {
1507 PrefetchConfigV2 {
1508 enable: v.enable,
1509 threads_count: v.threads_count,
1510 batch_size: v.batch_size,
1511 bandwidth_limit: v.bandwidth_limit,
1512 prefetch_all: true,
1513 }
1514 }
1515}
1516
1517#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1519pub(crate) struct BlobCacheEntryConfig {
1520 #[serde(default)]
1522 id: String,
1523 backend_type: String,
1525 backend_config: Value,
1529 #[serde(default)]
1531 external_backends: Vec<ExternalBackendConfig>,
1532 cache_type: String,
1536 cache_config: Value,
1540 #[serde(default)]
1542 prefetch_config: BlobPrefetchConfig,
1543 #[serde(default)]
1545 metadata_path: Option<String>,
1546}
1547
1548impl TryFrom<&BlobCacheEntryConfig> for BlobCacheEntryConfigV2 {
1549 type Error = std::io::Error;
1550
1551 fn try_from(v: &BlobCacheEntryConfig) -> std::result::Result<Self, Self::Error> {
1552 let backend_config = BackendConfig {
1553 backend_type: v.backend_type.clone(),
1554 backend_config: v.backend_config.clone(),
1555 };
1556 let cache_config = CacheConfig {
1557 cache_type: v.cache_type.clone(),
1558 cache_compressed: false,
1559 cache_config: v.cache_config.clone(),
1560 cache_validate: false,
1561 prefetch_config: v.prefetch_config.clone(),
1562 };
1563 Ok(BlobCacheEntryConfigV2 {
1564 version: 2,
1565 id: v.id.clone(),
1566 backend: (&backend_config).try_into()?,
1567 external_backends: v.external_backends.clone(),
1568 cache: (&cache_config).try_into()?,
1569 metadata_path: v.metadata_path.clone(),
1570 })
1571 }
1572}
1573
1574#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
1578pub struct OverlayConfig {
1579 pub upper_dir: String,
1580 pub work_dir: String,
1581}
1582
1583#[cfg(test)]
1584mod tests {
1585 use super::*;
1586 use crate::{BlobCacheEntry, BLOB_CACHE_TYPE_META_BLOB};
1587
1588 #[test]
1589 fn test_blob_prefetch_config() {
1590 let config = BlobPrefetchConfig::default();
1591 assert!(!config.enable);
1592 assert_eq!(config.threads_count, 0);
1593 assert_eq!(config.batch_size, 0);
1594 assert_eq!(config.bandwidth_limit, 0);
1595
1596 let content = r#"{
1597 "enable": true,
1598 "threads_count": 2,
1599 "merging_size": 4,
1600 "bandwidth_rate": 5
1601 }"#;
1602 let config: BlobPrefetchConfig = serde_json::from_str(content).unwrap();
1603 assert!(config.enable);
1604 assert_eq!(config.threads_count, 2);
1605 assert_eq!(config.batch_size, 4);
1606 assert_eq!(config.bandwidth_limit, 5);
1607
1608 let config: PrefetchConfigV2 = (&config).into();
1609 assert!(config.enable);
1610 assert_eq!(config.threads_count, 2);
1611 assert_eq!(config.batch_size, 4);
1612 assert_eq!(config.bandwidth_limit, 5);
1613 assert!(config.prefetch_all);
1614 }
1615
1616 #[test]
1617 fn test_file_cache_config() {
1618 let config: FileCacheConfig = serde_json::from_str("{}").unwrap();
1619 assert_eq!(&config.work_dir, ".");
1620 assert!(!config.disable_indexed_map);
1621
1622 let config: FileCacheConfig =
1623 serde_json::from_str("{\"work_dir\":\"/tmp\",\"disable_indexed_map\":true}").unwrap();
1624 assert_eq!(&config.work_dir, "/tmp");
1625 assert!(config.get_work_dir().is_ok());
1626 assert!(config.disable_indexed_map);
1627
1628 let config: FileCacheConfig =
1629 serde_json::from_str("{\"work_dir\":\"/proc/mounts\",\"disable_indexed_map\":true}")
1630 .unwrap();
1631 assert!(config.get_work_dir().is_err());
1632 }
1633
1634 #[test]
1635 fn test_fs_cache_config() {
1636 let config: FsCacheConfig = serde_json::from_str("{}").unwrap();
1637 assert_eq!(&config.work_dir, ".");
1638
1639 let config: FileCacheConfig = serde_json::from_str("{\"work_dir\":\"/tmp\"}").unwrap();
1640 assert_eq!(&config.work_dir, "/tmp");
1641 assert!(config.get_work_dir().is_ok());
1642
1643 let config: FileCacheConfig =
1644 serde_json::from_str("{\"work_dir\":\"/proc/mounts\"}").unwrap();
1645 assert!(config.get_work_dir().is_err());
1646 }
1647
1648 #[test]
1649 fn test_blob_cache_entry() {
1650 let content = r#"{
1651 "type": "bootstrap",
1652 "id": "blob1",
1653 "config": {
1654 "id": "cache1",
1655 "backend_type": "localfs",
1656 "backend_config": {},
1657 "cache_type": "fscache",
1658 "cache_config": {},
1659 "prefetch_config": {
1660 "enable": true,
1661 "threads_count": 2,
1662 "merging_size": 4,
1663 "bandwidth_rate": 5
1664 },
1665 "metadata_path": "/tmp/metadata1"
1666 },
1667 "domain_id": "domain1"
1668 }"#;
1669 let config: BlobCacheEntry = serde_json::from_str(content).unwrap();
1670 assert_eq!(&config.blob_type, BLOB_CACHE_TYPE_META_BLOB);
1671 assert_eq!(&config.blob_id, "blob1");
1672 assert_eq!(&config.domain_id, "domain1");
1673
1674 let blob_config = config.blob_config_legacy.as_ref().unwrap();
1675 assert_eq!(blob_config.id, "cache1");
1676 assert_eq!(blob_config.backend_type, "localfs");
1677 assert_eq!(blob_config.cache_type, "fscache");
1678 assert!(blob_config.cache_config.is_object());
1679 assert!(blob_config.prefetch_config.enable);
1680 assert_eq!(blob_config.prefetch_config.threads_count, 2);
1681 assert_eq!(blob_config.prefetch_config.batch_size, 4);
1682 assert_eq!(
1683 blob_config.metadata_path.as_ref().unwrap().as_str(),
1684 "/tmp/metadata1"
1685 );
1686
1687 let blob_config: BlobCacheEntryConfigV2 = blob_config.try_into().unwrap();
1688 assert_eq!(blob_config.id, "cache1");
1689 assert_eq!(blob_config.backend.backend_type, "localfs");
1690 assert_eq!(blob_config.cache.cache_type, "fscache");
1691 assert!(blob_config.cache.fs_cache.is_some());
1692 assert!(blob_config.cache.prefetch.enable);
1693 assert_eq!(blob_config.cache.prefetch.threads_count, 2);
1694 assert_eq!(blob_config.cache.prefetch.batch_size, 4);
1695 assert_eq!(
1696 blob_config.metadata_path.as_ref().unwrap().as_str(),
1697 "/tmp/metadata1"
1698 );
1699
1700 let content = r#"{
1701 "type": "bootstrap",
1702 "id": "blob1",
1703 "config": {
1704 "id": "cache1",
1705 "backend_type": "localfs",
1706 "backend_config": {},
1707 "cache_type": "fscache",
1708 "cache_config": {},
1709 "metadata_path": "/tmp/metadata1"
1710 },
1711 "domain_id": "domain1"
1712 }"#;
1713 let config: BlobCacheEntry = serde_json::from_str(content).unwrap();
1714 let blob_config = config.blob_config_legacy.as_ref().unwrap();
1715 assert!(!blob_config.prefetch_config.enable);
1716 assert_eq!(blob_config.prefetch_config.threads_count, 0);
1717 assert_eq!(blob_config.prefetch_config.batch_size, 0);
1718 }
1719
1720 #[test]
1721 fn test_proxy_config() {
1722 let content = r#"{
1723 "url": "foo.com",
1724 "ping_url": "ping.foo.com",
1725 "fallback": true
1726 }"#;
1727 let config: ProxyConfig = serde_json::from_str(content).unwrap();
1728 assert_eq!(config.url, "foo.com");
1729 assert_eq!(config.ping_url, "ping.foo.com");
1730 assert!(config.fallback);
1731 assert_eq!(config.check_interval, 5);
1732 }
1733
1734 #[test]
1735 fn test_oss_config() {
1736 let content = r#"{
1737 "endpoint": "test",
1738 "access_key_id": "test",
1739 "access_key_secret": "test",
1740 "bucket_name": "antsys-nydus",
1741 "object_prefix":"nydus_v2/"
1742 }"#;
1743 let config: OssConfig = serde_json::from_str(content).unwrap();
1744 assert_eq!(config.scheme, "https");
1745 assert!(!config.skip_verify);
1746 assert_eq!(config.timeout, 5);
1747 assert_eq!(config.connect_timeout, 5);
1748 }
1749
1750 #[test]
1751 fn test_s3_config() {
1752 let content = r#"{
1753 "endpoint": "test",
1754 "region": "us-east-1",
1755 "access_key_id": "test",
1756 "access_key_secret": "test",
1757 "bucket_name": "antsys-nydus",
1758 "object_prefix":"nydus_v2/"
1759 }"#;
1760 let config: OssConfig = serde_json::from_str(content).unwrap();
1761 assert_eq!(config.scheme, "https");
1762 assert!(!config.skip_verify);
1763 assert_eq!(config.timeout, 5);
1764 assert_eq!(config.connect_timeout, 5);
1765 }
1766
1767 #[test]
1768 fn test_registry_config() {
1769 let content = r#"{
1770 "scheme": "http",
1771 "skip_verify": true,
1772 "host": "my-registry:5000",
1773 "repo": "test/repo",
1774 "auth": "base64_encoded_auth",
1775 "registry_token": "bearer_token",
1776 "blob_redirected_host": "blob_redirected_host"
1777 }"#;
1778 let config: RegistryConfig = serde_json::from_str(content).unwrap();
1779 assert_eq!(config.scheme, "http");
1780 assert!(config.skip_verify);
1781 }
1782
1783 #[test]
1784 fn test_localfs_config() {
1785 let content = r#"{
1786 "blob_file": "blob_file",
1787 "dir": "blob_dir",
1788 "alt_dirs": ["dir1", "dir2"]
1789 }"#;
1790 let config: LocalFsConfig = serde_json::from_str(content).unwrap();
1791 assert_eq!(config.blob_file, "blob_file");
1792 assert_eq!(config.dir, "blob_dir");
1793 assert_eq!(config.alt_dirs, vec!["dir1", "dir2"]);
1794 }
1795
1796 #[test]
1797 fn test_localdisk_config() {
1798 let content = r#"{
1799 "device_path": "device_path"
1800 }"#;
1801 let config: LocalDiskConfig = serde_json::from_str(content).unwrap();
1802 assert_eq!(config.device_path, "device_path");
1803 }
1804
1805 #[test]
1806 fn test_backend_config() {
1807 let config = BackendConfig {
1808 backend_type: "localfs".to_string(),
1809 backend_config: Default::default(),
1810 };
1811 let str_val = serde_json::to_string(&config).unwrap();
1812 let config2 = serde_json::from_str(&str_val).unwrap();
1813
1814 assert_eq!(config, config2);
1815 }
1816
1817 #[test]
1818 fn test_v2_version() {
1819 let content = "version=2";
1820 let config: ConfigV2 = toml::from_str(content).unwrap();
1821 assert_eq!(config.version, 2);
1822 assert!(config.backend.is_none());
1823 }
1824
1825 #[test]
1826 fn test_v2_backend() {
1827 let content = r#"version=2
1828 [backend]
1829 type = "localfs"
1830 "#;
1831 let config: ConfigV2 = toml::from_str(content).unwrap();
1832 assert_eq!(config.version, 2);
1833 assert!(config.backend.is_some());
1834 assert!(config.cache.is_none());
1835
1836 let backend = config.backend.as_ref().unwrap();
1837 assert_eq!(&backend.backend_type, "localfs");
1838 assert!(backend.localfs.is_none());
1839 assert!(backend.oss.is_none());
1840 assert!(backend.registry.is_none());
1841 }
1842
1843 #[test]
1844 fn test_v2_backend_localfs() {
1845 let content = r#"version=2
1846 [backend]
1847 type = "localfs"
1848 [backend.localfs]
1849 blob_file = "/tmp/nydus.blob.data"
1850 dir = "/tmp"
1851 alt_dirs = ["/var/nydus/cache"]
1852 "#;
1853 let config: ConfigV2 = toml::from_str(content).unwrap();
1854 assert_eq!(config.version, 2);
1855 assert!(config.backend.is_some());
1856
1857 let backend = config.backend.as_ref().unwrap();
1858 assert_eq!(&backend.backend_type, "localfs");
1859 assert!(backend.localfs.is_some());
1860
1861 let localfs = backend.localfs.as_ref().unwrap();
1862 assert_eq!(&localfs.blob_file, "/tmp/nydus.blob.data");
1863 assert_eq!(&localfs.dir, "/tmp");
1864 assert_eq!(&localfs.alt_dirs[0], "/var/nydus/cache");
1865 }
1866
1867 #[test]
1868 fn test_v2_backend_oss() {
1869 let content = r#"version=2
1870 [backend]
1871 type = "oss"
1872 [backend.oss]
1873 endpoint = "my_endpoint"
1874 bucket_name = "my_bucket_name"
1875 object_prefix = "my_object_prefix"
1876 access_key_id = "my_access_key_id"
1877 access_key_secret = "my_access_key_secret"
1878 scheme = "http"
1879 skip_verify = true
1880 timeout = 10
1881 connect_timeout = 10
1882 retry_limit = 5
1883 [backend.oss.proxy]
1884 url = "localhost:6789"
1885 ping_url = "localhost:6789/ping"
1886 fallback = true
1887 check_interval = 10
1888 use_http = true
1889 [[backend.oss.mirrors]]
1890 host = "http://127.0.0.1:65001"
1891 ping_url = "http://127.0.0.1:65001/ping"
1892 health_check_interval = 10
1893 failure_limit = 10
1894 "#;
1895 let config: ConfigV2 = toml::from_str(content).unwrap();
1896 assert_eq!(config.version, 2);
1897 assert!(config.backend.is_some());
1898 assert!(config.rafs.is_none());
1899
1900 let backend = config.backend.as_ref().unwrap();
1901 assert_eq!(&backend.backend_type, "oss");
1902 assert!(backend.oss.is_some());
1903
1904 let oss = backend.oss.as_ref().unwrap();
1905 assert_eq!(&oss.endpoint, "my_endpoint");
1906 assert_eq!(&oss.bucket_name, "my_bucket_name");
1907 assert_eq!(&oss.object_prefix, "my_object_prefix");
1908 assert_eq!(&oss.access_key_id, "my_access_key_id");
1909 assert_eq!(&oss.access_key_secret, "my_access_key_secret");
1910 assert_eq!(&oss.scheme, "http");
1911 assert!(oss.skip_verify);
1912 assert_eq!(oss.timeout, 10);
1913 assert_eq!(oss.connect_timeout, 10);
1914 assert_eq!(oss.retry_limit, 5);
1915 assert_eq!(&oss.proxy.url, "localhost:6789");
1916 assert_eq!(&oss.proxy.ping_url, "localhost:6789/ping");
1917 assert_eq!(oss.proxy.check_interval, 10);
1918 assert!(oss.proxy.fallback);
1919 assert!(oss.proxy.use_http);
1920
1921 assert_eq!(oss.mirrors.len(), 1);
1922 let mirror = &oss.mirrors[0];
1923 assert_eq!(mirror.host, "http://127.0.0.1:65001");
1924 assert_eq!(mirror.ping_url, "http://127.0.0.1:65001/ping");
1925 assert!(mirror.headers.is_empty());
1926 assert_eq!(mirror.health_check_interval, 10);
1927 assert_eq!(mirror.failure_limit, 10);
1928 }
1929
1930 #[test]
1931 fn test_v2_backend_registry() {
1932 let content = r#"version=2
1933 [backend]
1934 type = "registry"
1935 [backend.registry]
1936 scheme = "http"
1937 host = "localhost"
1938 repo = "nydus"
1939 auth = "auth"
1940 skip_verify = true
1941 timeout = 10
1942 connect_timeout = 10
1943 retry_limit = 5
1944 registry_token = "bear_token"
1945 blob_url_scheme = "https"
1946 blob_redirected_host = "redirect.registry.com"
1947 [backend.registry.proxy]
1948 url = "localhost:6789"
1949 ping_url = "localhost:6789/ping"
1950 fallback = true
1951 check_interval = 10
1952 use_http = true
1953 [[backend.registry.mirrors]]
1954 host = "http://127.0.0.1:65001"
1955 ping_url = "http://127.0.0.1:65001/ping"
1956 health_check_interval = 10
1957 failure_limit = 10
1958 "#;
1959 let config: ConfigV2 = toml::from_str(content).unwrap();
1960 assert_eq!(config.version, 2);
1961 assert!(config.backend.is_some());
1962 assert!(config.rafs.is_none());
1963
1964 let backend = config.backend.as_ref().unwrap();
1965 assert_eq!(&backend.backend_type, "registry");
1966 assert!(backend.registry.is_some());
1967
1968 let registry = backend.registry.as_ref().unwrap();
1969 assert_eq!(®istry.scheme, "http");
1970 assert_eq!(®istry.host, "localhost");
1971 assert_eq!(®istry.repo, "nydus");
1972 assert_eq!(registry.auth.as_ref().unwrap(), "auth");
1973 assert!(registry.skip_verify);
1974 assert_eq!(registry.timeout, 10);
1975 assert_eq!(registry.connect_timeout, 10);
1976 assert_eq!(registry.retry_limit, 5);
1977 assert_eq!(registry.registry_token.as_ref().unwrap(), "bear_token");
1978 assert_eq!(registry.blob_url_scheme, "https");
1979 assert_eq!(registry.blob_redirected_host, "redirect.registry.com");
1980
1981 assert_eq!(®istry.proxy.url, "localhost:6789");
1982 assert_eq!(®istry.proxy.ping_url, "localhost:6789/ping");
1983 assert_eq!(registry.proxy.check_interval, 10);
1984 assert!(registry.proxy.fallback);
1985 assert!(registry.proxy.use_http);
1986
1987 assert_eq!(registry.mirrors.len(), 1);
1988 let mirror = ®istry.mirrors[0];
1989 assert_eq!(mirror.host, "http://127.0.0.1:65001");
1990 assert_eq!(mirror.ping_url, "http://127.0.0.1:65001/ping");
1991 assert!(mirror.headers.is_empty());
1992 assert_eq!(mirror.health_check_interval, 10);
1993 assert_eq!(mirror.failure_limit, 10);
1994 }
1995
1996 #[test]
1997 fn test_v2_cache() {
1998 let content = r#"version=2
1999 [cache]
2000 type = "filecache"
2001 compressed = true
2002 validate = true
2003 [cache.filecache]
2004 work_dir = "/tmp"
2005 [cache.fscache]
2006 work_dir = "./"
2007 [cache.prefetch]
2008 enable = true
2009 threads = 8
2010 batch_size = 1000000
2011 bandwidth_limit = 10000000
2012 "#;
2013 let config: ConfigV2 = toml::from_str(content).unwrap();
2014 assert_eq!(config.version, 2);
2015 assert!(config.backend.is_none());
2016 assert!(config.rafs.is_none());
2017 assert!(config.cache.is_some());
2018
2019 let cache = config.cache.as_ref().unwrap();
2020 assert_eq!(&cache.cache_type, "filecache");
2021 assert!(cache.cache_compressed);
2022 assert!(cache.cache_validate);
2023 let filecache = cache.file_cache.as_ref().unwrap();
2024 assert_eq!(&filecache.work_dir, "/tmp");
2025 let fscache = cache.fs_cache.as_ref().unwrap();
2026 assert_eq!(&fscache.work_dir, "./");
2027
2028 let prefetch = &cache.prefetch;
2029 assert!(prefetch.enable);
2030 assert_eq!(prefetch.threads_count, 8);
2031 assert_eq!(prefetch.batch_size, 1000000);
2032 assert_eq!(prefetch.bandwidth_limit, 10000000);
2033 }
2034
2035 #[test]
2036 fn test_v2_rafs() {
2037 let content = r#"version=2
2038 [rafs]
2039 mode = "direct"
2040 batch_size = 1000000
2041 validate = true
2042 enable_xattr = true
2043 iostats_files = true
2044 access_pattern = true
2045 latest_read_files = true
2046 [rafs.prefetch]
2047 enable = true
2048 threads = 4
2049 batch_size = 1000000
2050 bandwidth_limit = 10000000
2051 prefetch_all = true
2052 "#;
2053 let config: ConfigV2 = toml::from_str(content).unwrap();
2054 assert_eq!(config.version, 2);
2055 assert!(config.backend.is_none());
2056 assert!(config.cache.is_none());
2057 assert!(config.rafs.is_some());
2058
2059 let rafs = config.rafs.as_ref().unwrap();
2060 assert_eq!(&rafs.mode, "direct");
2061 assert_eq!(rafs.user_io_batch_size, 1000000);
2062 assert!(rafs.validate);
2063 assert!(rafs.enable_xattr);
2064 assert!(rafs.iostats_files);
2065 assert!(rafs.access_pattern);
2066 assert!(rafs.latest_read_files);
2067 assert!(rafs.prefetch.enable);
2068 assert_eq!(rafs.prefetch.threads_count, 4);
2069 assert_eq!(rafs.prefetch.batch_size, 1000000);
2070 assert_eq!(rafs.prefetch.bandwidth_limit, 10000000);
2071 assert!(rafs.prefetch.prefetch_all)
2072 }
2073
2074 #[test]
2075 fn test_v2_blob_cache_entry() {
2076 let content = r#"version=2
2077 id = "my_id"
2078 metadata_path = "meta_path"
2079 [backend]
2080 type = "localfs"
2081 [backend.localfs]
2082 blob_file = "/tmp/nydus.blob.data"
2083 dir = "/tmp"
2084 alt_dirs = ["/var/nydus/cache"]
2085 [cache]
2086 type = "filecache"
2087 compressed = true
2088 validate = true
2089 [cache.filecache]
2090 work_dir = "/tmp"
2091 "#;
2092 let config: BlobCacheEntryConfigV2 = toml::from_str(content).unwrap();
2093 assert_eq!(config.version, 2);
2094 assert_eq!(&config.id, "my_id");
2095 assert_eq!(config.metadata_path.as_ref().unwrap(), "meta_path");
2096
2097 let backend = &config.backend;
2098 assert_eq!(&backend.backend_type, "localfs");
2099 assert!(backend.localfs.is_some());
2100
2101 let localfs = backend.localfs.as_ref().unwrap();
2102 assert_eq!(&localfs.blob_file, "/tmp/nydus.blob.data");
2103 assert_eq!(&localfs.dir, "/tmp");
2104 assert_eq!(&localfs.alt_dirs[0], "/var/nydus/cache");
2105 }
2106
2107 #[test]
2108 fn test_sample_config_file() {
2109 let content = r#"{
2110 "device": {
2111 "backend": {
2112 "type": "localfs",
2113 "config": {
2114 "dir": "/tmp/AM7TxD/blobs",
2115 "readahead": true
2116 }
2117 },
2118 "cache": {
2119 "type": "blobcache",
2120 "compressed": true,
2121 "config": {
2122 "work_dir": "/tmp/AM7TxD/cache"
2123 }
2124 }
2125 },
2126 "mode": "cached",
2127 "digest_validate": true,
2128 "iostats_files": false
2129 }
2130 "#;
2131 let config = ConfigV2::from_str(content).unwrap();
2132 assert_eq!(&config.id, "");
2133 }
2134
2135 #[test]
2136 fn test_snapshotter_sample_config() {
2137 let content = r#"
2138 {
2139 "device": {
2140 "backend": {
2141 "type": "registry",
2142 "config": {
2143 "readahead": false,
2144 "host": "localhost",
2145 "repo": "vke/golang",
2146 "auth": "",
2147 "scheme": "https",
2148 "proxy": {
2149 "fallback": false
2150 },
2151 "timeout": 5,
2152 "connect_timeout": 5,
2153 "retry_limit": 2
2154 }
2155 },
2156 "cache": {
2157 "type": "blobcache",
2158 "compressed": true,
2159 "config": {
2160 "work_dir": "/var/lib/containerd/io.containerd.snapshotter.v1.nydus/cache",
2161 "disable_indexed_map": false
2162 }
2163 }
2164 },
2165 "mode": "direct",
2166 "digest_validate": false,
2167 "enable_xattr": true,
2168 "fs_prefetch": {
2169 "enable": true,
2170 "prefetch_all": true,
2171 "threads_count": 8,
2172 "merging_size": 1048576,
2173 "bandwidth_rate": 0
2174 }
2175 }
2176 "#;
2177 let config = ConfigV2::from_str(content).unwrap();
2178 assert_eq!(&config.id, "");
2179 }
2180
2181 #[test]
2182 fn test_backend_http_proxy_config() {
2183 let config =
2184 r#"{"version":2,"backend":{"type":"http-proxy","http-proxy":{"addr":"/tmp"}}}"#;
2185 let config = ConfigV2::from_str(config).unwrap();
2186 let backend = config.backend.unwrap();
2187 assert_eq!(&backend.backend_type, "http-proxy");
2188 assert_eq!(&backend.http_proxy.unwrap().addr, "/tmp");
2189 }
2190
2191 #[test]
2192 fn test_new_localfs() {
2193 let config = ConfigV2::new_localfs("id1", "./").unwrap();
2194 assert_eq!(&config.id, "id1");
2195 assert_eq!(config.backend.as_ref().unwrap().backend_type, "localfs");
2196 }
2197
2198 #[test]
2199 fn test_update_registry_auth_info() {
2200 let config = r#"
2201 {
2202 "device": {
2203 "id": "test",
2204 "backend": {
2205 "type": "registry",
2206 "config": {
2207 "readahead": false,
2208 "host": "docker.io",
2209 "repo": "library/nginx",
2210 "scheme": "https",
2211 "proxy": {
2212 "fallback": false
2213 },
2214 "timeout": 5,
2215 "connect_timeout": 5,
2216 "retry_limit": 8
2217 }
2218 }
2219 },
2220 "mode": "direct",
2221 "digest_validate": false,
2222 "enable_xattr": true,
2223 "fs_prefetch": {
2224 "enable": true,
2225 "threads_count": 10,
2226 "merging_size": 131072,
2227 "bandwidth_rate": 10485760
2228 }
2229 }"#;
2230
2231 let mut rafs_config = ConfigV2::from_str(&config).unwrap();
2232 let test_auth = "test_auth".to_string();
2233
2234 rafs_config.update_registry_auth_info(&Some(test_auth.clone()));
2235
2236 let backend = rafs_config.backend.unwrap();
2237 let registry = backend.registry.unwrap();
2238 let auth = registry.auth.unwrap();
2239 assert_eq!(auth, test_auth);
2240 }
2241
2242 #[test]
2243 fn test_config2_error() {
2244 let content_bad_version = r#"version=3
2245 "#;
2246 let cfg: ConfigV2 = toml::from_str(content_bad_version).unwrap();
2247 assert!(!cfg.validate());
2248 let cfg = ConfigV2::new("id");
2249 assert!(cfg.get_backend_config().is_err());
2250 assert!(cfg.get_cache_config().is_err());
2251 assert!(cfg.get_rafs_config().is_err());
2252 assert!(cfg.get_cache_working_directory().is_err());
2253
2254 let content = r#"version=2
2255 [cache]
2256 type = "filecache"
2257 [cache.filecache]
2258 work_dir = "/tmp"
2259 "#;
2260 let cfg: ConfigV2 = toml::from_str(content).unwrap();
2261 assert_eq!(cfg.get_cache_working_directory().unwrap(), "/tmp");
2262
2263 let content = r#"version=2
2264 [cache]
2265 type = "fscache"
2266 [cache.fscache]
2267 work_dir = "./foo"
2268 "#;
2269 let cfg: ConfigV2 = toml::from_str(content).unwrap();
2270 assert_eq!(cfg.get_cache_working_directory().unwrap(), "./foo");
2271
2272 let content = r#"version=2
2273 [cache]
2274 type = "bar"
2275 "#;
2276 let cfg: ConfigV2 = toml::from_str(content).unwrap();
2277 assert!(cfg.get_cache_working_directory().is_err());
2278
2279 let content = r#"
2280 foo-bar-xxxx
2281 "#;
2282 assert!(toml::from_str::<ConfigV2>(content).is_err());
2283 }
2284
2285 #[test]
2286 fn test_backend_config_valid() {
2287 let mut cfg = BackendConfigV2 {
2288 backend_type: "localdisk".to_string(),
2289 ..Default::default()
2290 };
2291 assert!(!cfg.validate());
2292 cfg.localdisk = Some(LocalDiskConfig {
2293 device_path: "".to_string(),
2294 disable_gpt: true,
2295 });
2296 assert!(!cfg.validate());
2297
2298 let cfg = BackendConfigV2 {
2299 backend_type: "localfs".to_string(),
2300 ..Default::default()
2301 };
2302 assert!(!cfg.validate());
2303
2304 let cfg = BackendConfigV2 {
2305 backend_type: "oss".to_string(),
2306 ..Default::default()
2307 };
2308 assert!(!cfg.validate());
2309
2310 let cfg = BackendConfigV2 {
2311 backend_type: "s3".to_string(),
2312 ..Default::default()
2313 };
2314 assert!(!cfg.validate());
2315
2316 let cfg = BackendConfigV2 {
2317 backend_type: "register".to_string(),
2318 ..Default::default()
2319 };
2320 assert!(!cfg.validate());
2321
2322 let cfg = BackendConfigV2 {
2323 backend_type: "http-proxy".to_string(),
2324 ..Default::default()
2325 };
2326 assert!(!cfg.validate());
2327
2328 let cfg = BackendConfigV2 {
2329 backend_type: "foobar".to_string(),
2330 ..Default::default()
2331 };
2332 assert!(!cfg.validate());
2333 }
2334
2335 fn get_config(backend_type: &str) {
2336 let mut cfg: BackendConfigV2 = BackendConfigV2::default();
2337 assert!(cfg.get_localdisk_config().is_err());
2338
2339 cfg.backend_type = backend_type.to_string();
2340 assert!(cfg.get_localdisk_config().is_err());
2341 }
2342
2343 #[test]
2344 fn test_get_config() {
2345 get_config("localdisk");
2346 get_config("localfs");
2347 get_config("oss");
2348 get_config("s3");
2349 get_config("register");
2350 get_config("http-proxy");
2351 }
2352
2353 #[test]
2354 fn test_cache_config_valid() {
2355 let cfg = CacheConfigV2 {
2356 cache_type: "blobcache".to_string(),
2357 ..Default::default()
2358 };
2359 assert!(!cfg.validate());
2360
2361 let cfg = CacheConfigV2 {
2362 cache_type: "fscache".to_string(),
2363 ..Default::default()
2364 };
2365 assert!(!cfg.validate());
2366
2367 let cfg = CacheConfigV2 {
2368 cache_type: "dummycache".to_string(),
2369 ..Default::default()
2370 };
2371 assert!(cfg.validate());
2372
2373 let cfg = CacheConfigV2 {
2374 cache_type: "foobar".to_string(),
2375 ..Default::default()
2376 };
2377 assert!(!cfg.validate());
2378 }
2379
2380 #[test]
2381 fn test_get_fscache_config() {
2382 let mut cfg = CacheConfigV2::default();
2383 assert!(cfg.get_fscache_config().is_err());
2384 cfg.cache_type = "fscache".to_string();
2385 assert!(cfg.get_fscache_config().is_err());
2386 }
2387
2388 #[test]
2389 fn test_fscache_get_work_dir() {
2390 let mut cfg = FsCacheConfig::default();
2391 assert!(cfg.get_work_dir().is_err());
2392 cfg.work_dir = ".".to_string();
2393 assert!(cfg.get_work_dir().is_ok());
2394 cfg.work_dir = "foobar".to_string();
2395 let res = cfg.get_work_dir().is_ok();
2396 fs::remove_dir_all("foobar").unwrap();
2397 assert!(res);
2398 }
2399
2400 #[test]
2401 fn test_default_mirror_config() {
2402 let cfg = MirrorConfig::default();
2403 assert_eq!(cfg.host, "");
2404 assert_eq!(cfg.health_check_interval, 5);
2405 assert_eq!(cfg.failure_limit, 5);
2406 assert_eq!(cfg.ping_url, "");
2407 }
2408
2409 #[test]
2410 fn test_config_v2_from_file() {
2411 let content = r#"version=2
2412 [cache]
2413 type = "filecache"
2414 [cache.filecache]
2415 work_dir = "/tmp"
2416 "#;
2417 if fs::write("test_config_v2_from_file.cfg", content).is_ok() {
2418 let res = ConfigV2::from_file("test_config_v2_from_file.cfg").is_ok();
2419 fs::remove_file("test_config_v2_from_file.cfg").unwrap();
2420 assert!(res);
2421 } else {
2422 assert!(ConfigV2::from_file("test_config_v2_from_file.cfg").is_err());
2423 }
2424 }
2425
2426 #[test]
2427 fn test_blob_cache_entry_v2_from_file() {
2428 let content = r#"version=2
2429 id = "my_id"
2430 metadata_path = "meta_path"
2431 [backend]
2432 type = "localfs"
2433 [backend.localfs]
2434 blob_file = "/tmp/nydus.blob.data"
2435 dir = "/tmp"
2436 alt_dirs = ["/var/nydus/cache"]
2437 [cache]
2438 type = "filecache"
2439 compressed = true
2440 validate = true
2441 [cache.filecache]
2442 work_dir = "/tmp"
2443 "#;
2444 if fs::write("test_blob_cache_entry_v2_from_file.cfg", content).is_ok() {
2445 let res =
2446 BlobCacheEntryConfigV2::from_file("test_blob_cache_entry_v2_from_file.cfg").is_ok();
2447 fs::remove_file("test_blob_cache_entry_v2_from_file.cfg").unwrap();
2448 assert!(res);
2449 } else {
2450 assert!(ConfigV2::from_file("test_blob_cache_entry_v2_from_file.cfg").is_err());
2451 }
2452 }
2453
2454 #[test]
2455 fn test_blob_cache_valid() {
2456 let err_version_content = r#"version=1"#;
2457
2458 let config: BlobCacheEntryConfigV2 = toml::from_str(err_version_content).unwrap();
2459 assert!(!config.validate());
2460
2461 let content = r#"version=2
2462 id = "my_id"
2463 metadata_path = "meta_path"
2464 [backend]
2465 type = "localfs"
2466 [backend.localfs]
2467 blob_file = "/tmp/nydus.blob.data"
2468 dir = "/tmp"
2469 alt_dirs = ["/var/nydus/cache"]
2470 [cache]
2471 type = "filecache"
2472 compressed = true
2473 validate = true
2474 [cache.filecache]
2475 work_dir = "/tmp"
2476 "#;
2477
2478 let config: BlobCacheEntryConfigV2 = toml::from_str(content).unwrap();
2479 assert!(config.validate());
2480 }
2481
2482 #[test]
2483 fn test_blob_from_str() {
2484 let content = r#"version=2
2485 id = "my_id"
2486 metadata_path = "meta_path"
2487 [backend]
2488 type = "localfs"
2489 [backend.localfs]
2490 blob_file = "/tmp/nydus.blob.data"
2491 dir = "/tmp"
2492 alt_dirs = ["/var/nydus/cache"]
2493 [cache]
2494 type = "filecache"
2495 compressed = true
2496 validate = true
2497 [cache.filecache]
2498 work_dir = "/tmp"
2499 "#;
2500
2501 let config: BlobCacheEntryConfigV2 = BlobCacheEntryConfigV2::from_str(content).unwrap();
2502 assert_eq!(config.version, 2);
2503 assert_eq!(config.id, "my_id");
2504 assert_eq!(config.backend.localfs.unwrap().dir, "/tmp");
2505 assert_eq!(config.cache.file_cache.unwrap().work_dir, "/tmp");
2506 let content = r#"
2507 {
2508 "version": 2,
2509 "id": "my_id",
2510 "backend": {
2511 "type": "localfs",
2512 "localfs": {
2513 "dir": "/tmp"
2514 }
2515 }
2516 }
2517 "#;
2518 let config: BlobCacheEntryConfigV2 = BlobCacheEntryConfigV2::from_str(content).unwrap();
2519
2520 assert_eq!(config.version, 2);
2521 assert_eq!(config.id, "my_id");
2522 assert_eq!(config.backend.localfs.unwrap().dir, "/tmp");
2523
2524 let content = r#"foobar"#;
2525 assert!(BlobCacheEntryConfigV2::from_str(content).is_err());
2526 }
2527
2528 #[test]
2529 fn test_blob_cache_entry_from_file() {
2530 let content = r#"{
2531 "type": "bootstrap",
2532 "id": "blob1",
2533 "config": {
2534 "id": "cache1",
2535 "backend_type": "localfs",
2536 "backend_config": {},
2537 "cache_type": "fscache",
2538 "cache_config": {},
2539 "metadata_path": "/tmp/metadata1"
2540 },
2541 "domain_id": "domain1"
2542 }"#;
2543 if fs::write("test_blob_cache_entry_from_file.cfg", content).is_ok() {
2544 let res = BlobCacheEntry::from_file("test_blob_cache_entry_from_file.cfg").is_ok();
2545 fs::remove_file("test_blob_cache_entry_from_file.cfg").unwrap();
2546 assert!(res);
2547 } else {
2548 assert!(ConfigV2::from_file("test_blob_cache_entry_from_file.cfg").is_err());
2549 }
2550 }
2551
2552 #[test]
2553 fn test_blob_cache_entry_valid() {
2554 let content = r#"{
2555 "type": "bootstrap",
2556 "id": "blob1",
2557 "config": {
2558 "id": "cache1",
2559 "backend_type": "localfs",
2560 "backend_config": {},
2561 "cache_type": "fscache",
2562 "cache_config": {},
2563 "metadata_path": "/tmp/metadata1"
2564 },
2565 "domain_id": "domain1"
2566 }"#;
2567 let mut cfg = BlobCacheEntry::from_str(content).unwrap();
2568 cfg.blob_type = "foobar".to_string();
2569 assert!(!cfg.validate());
2570
2571 let content = r#"{
2572 "type": "bootstrap",
2573 "id": "blob1",
2574 "domain_id": "domain1"
2575 }"#;
2576 let cfg = BlobCacheEntry::from_str(content).unwrap();
2577 assert!(cfg.validate());
2578 }
2579
2580 #[test]
2581 fn test_blob_cache_entry_from_str() {
2582 let content = r#"{
2583 "type": "bootstrap",
2584 "id": "blob1",
2585 "config": {
2586 "id": "cache1",
2587 "backend_type": "localfs",
2588 "backend_config": {},
2589 "cache_type": "fscache",
2590 "cache_config": {},
2591 "metadata_path": "/tmp/metadata1"
2592 },
2593 "domain_id": "domain1"
2594 }"#;
2595 assert!(BlobCacheEntry::from_str(content).is_ok());
2596 let content = r#"{
2597 "type": "foobar",
2598 "id": "blob1",
2599 "config": {
2600 "id": "cache1",
2601 "backend_type": "foobar",
2602 "backend_config": {},
2603 "cache_type": "foobar",
2604 "cache_config": {},
2605 "metadata_path": "/tmp/metadata1"
2606 },
2607 "domain_id": "domain1"
2608 }"#;
2609 assert!(BlobCacheEntry::from_str(content).is_err());
2610
2611 let content = r#"foobar"#;
2612 assert!(BlobCacheEntry::from_str(content).is_err());
2613 }
2614
2615 #[test]
2616 fn test_default_value() {
2617 assert!(default_true());
2618 assert_eq!(default_failure_limit(), 5);
2619 assert_eq!(default_prefetch_batch_size(), 1024 * 1024);
2620 assert_eq!(default_prefetch_threads_count(), 8);
2621 }
2622
2623 #[test]
2624 fn test_backend_config_try_from() {
2625 let config = BackendConfig {
2626 backend_type: "localdisk".to_string(),
2627 backend_config: serde_json::to_value(LocalDiskConfig::default()).unwrap(),
2628 };
2629 assert!(BackendConfigV2::try_from(&config).is_ok());
2630
2631 let config = BackendConfig {
2632 backend_type: "localfs".to_string(),
2633 backend_config: serde_json::to_value(LocalFsConfig::default()).unwrap(),
2634 };
2635 assert!(BackendConfigV2::try_from(&config).is_ok());
2636
2637 let config = BackendConfig {
2638 backend_type: "oss".to_string(),
2639 backend_config: serde_json::to_value(OssConfig::default()).unwrap(),
2640 };
2641 assert!(BackendConfigV2::try_from(&config).is_ok());
2642
2643 let config = BackendConfig {
2644 backend_type: "s3".to_string(),
2645 backend_config: serde_json::to_value(S3Config::default()).unwrap(),
2646 };
2647 assert!(BackendConfigV2::try_from(&config).is_ok());
2648
2649 let config = BackendConfig {
2650 backend_type: "registry".to_string(),
2651 backend_config: serde_json::to_value(RegistryConfig::default()).unwrap(),
2652 };
2653 assert!(BackendConfigV2::try_from(&config).is_ok());
2654
2655 let config = BackendConfig {
2656 backend_type: "foobar".to_string(),
2657 backend_config: serde_json::to_value(LocalDiskConfig::default()).unwrap(),
2658 };
2659 assert!(BackendConfigV2::try_from(&config).is_err());
2660 }
2661}