qiniu_download/config/
mod.rs

1mod configurable;
2mod http_client;
3mod multi_clusters;
4mod single_cluster;
5mod static_vars;
6mod watcher;
7
8pub use configurable::Configurable;
9use http_client::ensure_http_clients;
10pub(crate) use http_client::Timeouts;
11pub use multi_clusters::{
12    MultipleClustersConfig, MultipleClustersConfigBuilder, MultipleClustersConfigParseError,
13};
14pub use single_cluster::{Config, ConfigBuilder, SingleClusterConfig, SingleClusterConfigBuilder};
15
16use super::base::{credential::Credential, download::RangeReaderBuilder as BaseRangeReaderBuilder};
17use log::{error, info, warn};
18use static_vars::qiniu_config;
19use std::{env, fs, sync::RwLock, time::Duration};
20use tap::prelude::*;
21use thiserror::Error;
22use watcher::{ensure_watches, unwatch_all};
23
24/// 判断当前是否已经启用七牛环境
25///
26/// 如果当前没有设置 QINIU 环境变量,或加载该环境变量出现错误,则返回 false
27#[inline]
28pub fn is_qiniu_enabled() -> bool {
29    with_current_qiniu_config(|config| config.is_some())
30}
31
32/// 在回调函数内获取只读的当前七牛环境配置
33///
34/// 需要注意的是,在回调函数内,当前七牛环境配置会被用读锁保护,因此回调函数应该尽快返回
35#[inline]
36pub fn with_current_qiniu_config<T>(f: impl FnOnce(Option<&Configurable>) -> T) -> T {
37    f(qiniu_config().read().unwrap().as_ref())
38}
39
40/// 在回调函数内获取可写的当前七牛环境配置
41///
42/// 需要注意的是,在回调函数内,当前七牛环境配置会被用写锁保护,因此回调函数应该尽快返回
43#[inline]
44pub fn with_current_qiniu_config_mut<T>(f: impl FnOnce(&mut Option<Configurable>) -> T) -> T {
45    let result = f(&mut qiniu_config().write().unwrap());
46    with_current_qiniu_config(|config| {
47        ensure_watches_for(config);
48        ensure_http_clients_for(config);
49    });
50    result
51}
52
53/// 手动设置单集群七牛环境配置
54#[inline]
55pub fn set_qiniu_config(config: Config) {
56    set_config_and_reload(config.into(), false)
57}
58
59/// 手动设置单集群七牛环境配置
60#[inline]
61pub fn set_qiniu_single_cluster_config(config: SingleClusterConfig) {
62    set_qiniu_config(config)
63}
64
65/// 手动设置多集群七牛环境配置
66#[inline]
67pub fn set_qiniu_multi_clusters_config(config: MultipleClustersConfig) {
68    set_config_and_reload(config.into(), false)
69}
70
71const QINIU_ENV: &str = "QINIU";
72const QINIU_MULTI_ENV: &str = "QINIU_MULTI_CLUSTER";
73const QINIU_DISABLE_CONFIG_HOT_RELOADING_ENV: &str = "QINIU_DISABLE_CONFIG_HOT_RELOADING";
74
75fn load_config() -> Option<Configurable> {
76    return env::var_os(QINIU_MULTI_ENV)
77        .map(|path| (path, EnvFrom::FromQiniuMulti))
78        .or_else(|| env::var_os(QINIU_ENV).map(|path| (path, EnvFrom::FromQiniu)))
79        .tap_none(|| warn!("QINIU or QINIU_MULTI_CLUSTER Env IS NOT ENABLED"))
80        .and_then(|(qiniu_config_path, env_from)| {
81            fs::read(&qiniu_config_path)
82                .tap_err(|err| {
83                    error!(
84                        "Qiniu config file ({:?}) cannot be open: {}",
85                        qiniu_config_path, err
86                    )
87                })
88                .ok()
89                .and_then(|qiniu_config| {
90                    Configurable::parse(
91                        &qiniu_config_path,
92                        &qiniu_config,
93                        matches!(env_from, EnvFrom::FromQiniuMulti),
94                    )
95                    .tap_err(|err| {
96                        error!(
97                            "Qiniu config file ({:?}) cannot be deserialized: {}",
98                            qiniu_config_path, err
99                        )
100                    })
101                    .ok()
102                })
103        });
104
105    enum EnvFrom {
106        FromQiniu,
107        FromQiniuMulti,
108    }
109}
110
111fn init_config() -> RwLock<Option<Configurable>> {
112    RwLock::new(load_config().tap(|config| ensure_watches_for(config.as_ref())))
113}
114
115fn ensure_watches_for(config: Option<&Configurable>) {
116    if env::var_os(QINIU_DISABLE_CONFIG_HOT_RELOADING_ENV).is_none() {
117        if let Some(config) = config {
118            ensure_watches(&config.config_paths()).ok();
119        } else {
120            unwatch_all().ok();
121        }
122    }
123}
124
125fn ensure_http_clients_for(config: Option<&Configurable>) {
126    if let Some(config) = config {
127        ensure_http_clients(&config.timeouts_set());
128    } else {
129        ensure_http_clients(&Default::default());
130    }
131}
132
133fn reload_config(migrate_callback: bool) {
134    if let Some(config) = load_config() {
135        set_config_and_reload(config, migrate_callback)
136    }
137}
138
139fn set_config_and_reload(mut config: Configurable, migrate_callback: bool) {
140    with_current_qiniu_config_mut(|current| {
141        if migrate_callback {
142            if let (Some(current), Some(new)) = (
143                current.as_mut().and_then(|current| current.as_multi_mut()),
144                config.as_multi_mut(),
145            ) {
146                new.set_config_select_callback_raw(current.take_config_select_callback());
147            }
148        }
149        info!("QINIU_CONFIG reloaded: {:?}", config);
150        *current = Some(config);
151    });
152}
153
154/// 七牛配置信息解析错误
155#[derive(Error, Debug)]
156#[non_exhaustive]
157pub enum ClustersConfigParseError {
158    /// 七牛配置信息 JSON 解析错误
159    #[error("Parse config as json error: {0}")]
160    JSONError(#[from] serde_json::Error),
161
162    /// 七牛配置信息 TOML 解析错误
163    #[error("Parse config as toml error: {0}")]
164    TOMLError(#[from] toml::de::Error),
165}
166
167pub(super) fn build_range_reader_builder_from_config(
168    key: String,
169    config: &Config,
170) -> BaseRangeReaderBuilder {
171    let mut builder = BaseRangeReaderBuilder::new(
172        config.bucket().to_owned(),
173        key,
174        Credential::new(config.access_key(), config.secret_key()),
175        config
176            .io_urls()
177            .map(|urls| urls.to_owned())
178            .unwrap_or_default(),
179    );
180
181    if let Some(uc_urls) = config.uc_urls() {
182        if !uc_urls.is_empty() {
183            builder = builder.uc_urls(uc_urls.to_owned());
184        }
185    }
186
187    if let Some(monitor_urls) = config.monitor_urls() {
188        if !monitor_urls.is_empty() {
189            builder = builder.monitor_urls(monitor_urls.to_owned());
190        }
191    }
192
193    if let Some(retry) = config.retry() {
194        if retry > 0 {
195            builder = builder.io_tries(retry).uc_tries(retry).dot_tries(retry);
196        }
197    }
198
199    if let Some(punish_time) = config.punish_time() {
200        if punish_time > Duration::from_secs(0) {
201            builder = builder.punish_duration(punish_time);
202        }
203    }
204
205    if let Some(base_timeout) = config.base_timeout() {
206        if base_timeout > Duration::from_millis(0) {
207            builder = builder.base_timeout(base_timeout);
208        }
209    }
210
211    if let Some(connect_timeout) = config.connect_timeout() {
212        if connect_timeout > Duration::from_millis(0) {
213            builder = builder.connect_timeout(connect_timeout);
214        }
215    }
216
217    if let Some(dot_interval) = config.dot_interval() {
218        if dot_interval > Duration::from_secs(0) {
219            builder = builder.dot_interval(dot_interval);
220        }
221    }
222
223    if let Some(max_dot_buffer_size) = config.max_dot_buffer_size() {
224        if max_dot_buffer_size > 0 {
225            builder = builder.max_dot_buffer_size(max_dot_buffer_size);
226        }
227    }
228
229    if let Some(max_retry_concurrency) = config.max_retry_concurrency() {
230        builder = builder.max_retry_concurrency(max_retry_concurrency);
231    }
232
233    if let Some(true) = config.private() {
234        builder = builder.private_url_lifetime(Some(Duration::from_secs(3600)));
235    }
236
237    if let Some(use_getfile_api) = config.use_getfile_api() {
238        builder = builder.use_getfile_api(use_getfile_api);
239    }
240
241    if let Some(normalize_key) = config.normalize_key() {
242        builder = builder.normalize_key(normalize_key);
243    }
244
245    builder
246}
247
248pub(super) fn build_range_reader_builder_from_env(
249    key: String,
250    only_single_cluster: bool,
251) -> Option<BaseRangeReaderBuilder> {
252    with_current_qiniu_config(|config| {
253        config.and_then(|config| {
254            if only_single_cluster && config.as_single().is_some() {
255                return None;
256            }
257            config.with_key(&key.to_owned(), move |config| {
258                build_range_reader_builder_from_config(key, config)
259            })
260        })
261    })
262}
263
264#[cfg(test)]
265mod tests {
266    use super::{super::RangeReader, static_vars::reset_static_vars, *};
267    use anyhow::Result;
268    use std::{
269        collections::HashMap,
270        ffi::OsStr,
271        fs::{remove_file, rename, OpenOptions},
272        io::Write,
273        path::PathBuf,
274        thread::sleep,
275    };
276    use tempfile::{tempdir, Builder as TempFileBuilder};
277    use watcher::{watch_dirs_count, watch_files_count};
278
279    #[test]
280    fn test_load_config() -> Result<()> {
281        env_logger::try_init().ok();
282        let _defer = ResetFinally;
283
284        let mut config = ConfigBuilder::new(
285            "test-ak-1",
286            "test-sk-1",
287            "test-bucket-1",
288            Some(vec!["http://io1.com".into(), "http://io2.com".into()]),
289        )
290        .build();
291
292        let tempfile_path = {
293            let mut tempfile = TempFileBuilder::new().suffix(".toml").tempfile()?;
294            tempfile.write_all(&toml::to_vec(&config)?)?;
295            tempfile.flush()?;
296            tempfile.into_temp_path()
297        };
298        let _env_guard = QiniuEnvGuard::new(tempfile_path.as_os_str());
299
300        with_current_qiniu_config(|loaded| {
301            assert_eq!(loaded.and_then(|c| c.as_single()), Some(&config));
302        });
303
304        config.set_access_key("test-ak-2");
305        config.set_secret_key("test-sk-2");
306        config.set_bucket("test-bucket-2");
307
308        {
309            let mut tempfile = OpenOptions::new()
310                .write(true)
311                .truncate(true)
312                .open(&tempfile_path)?;
313            tempfile.write_all(&toml::to_vec(&config)?)?;
314            tempfile.flush()?;
315        }
316
317        sleep(Duration::from_secs(1));
318
319        with_current_qiniu_config(|loaded| {
320            let config = loaded.and_then(|c| c.as_single()).unwrap();
321            assert_eq!(config.access_key(), "test-ak-2");
322            assert_eq!(config.secret_key(), "test-sk-2");
323            assert_eq!(config.bucket(), "test-bucket-2");
324        });
325
326        config.set_access_key("test-ak-3");
327        config.set_secret_key("test-sk-3");
328        config.set_bucket("test-bucket-3");
329
330        {
331            remove_file(&tempfile_path)?;
332            let mut tempfile = OpenOptions::new()
333                .write(true)
334                .create(true)
335                .truncate(true)
336                .open(&tempfile_path)?;
337            tempfile.write_all(&toml::to_vec(&config)?)?;
338            tempfile.flush()?;
339        }
340
341        sleep(Duration::from_secs(1));
342
343        with_current_qiniu_config(|loaded| {
344            let config = loaded.and_then(|c| c.as_single()).unwrap();
345            assert_eq!(config.access_key(), "test-ak-3");
346            assert_eq!(config.secret_key(), "test-sk-3");
347            assert_eq!(config.bucket(), "test-bucket-3");
348        });
349
350        config.set_access_key("test-ak-4");
351        config.set_secret_key("test-sk-4");
352        config.set_bucket("test-bucket-4");
353
354        {
355            let new_tempfile_path = {
356                let mut new_path = tempfile_path.to_owned().into_os_string();
357                new_path.push(".tmp");
358                new_path
359            };
360            let mut tempfile = OpenOptions::new()
361                .write(true)
362                .create(true)
363                .truncate(true)
364                .open(&new_tempfile_path)?;
365            tempfile.write_all(&toml::to_vec(&config)?)?;
366            tempfile.flush()?;
367            rename(&new_tempfile_path, &tempfile_path)?;
368        }
369
370        sleep(Duration::from_secs(1));
371
372        with_current_qiniu_config(|loaded| {
373            let config = loaded.and_then(|c| c.as_single()).unwrap();
374            assert_eq!(config.access_key(), "test-ak-4");
375            assert_eq!(config.secret_key(), "test-sk-4");
376            assert_eq!(config.bucket(), "test-bucket-4");
377        });
378
379        config.set_access_key("test-ak-5");
380        config.set_secret_key("test-sk-5");
381        config.set_bucket("test-bucket-5");
382
383        {
384            let new_tempfile_path = {
385                let mut new_path = tempfile_path.to_owned().into_os_string();
386                new_path.push(".tmp");
387                new_path
388            };
389            let mut tempfile = OpenOptions::new()
390                .write(true)
391                .create(true)
392                .truncate(true)
393                .open(&new_tempfile_path)?;
394            tempfile.write_all(&toml::to_vec(&config)?)?;
395            tempfile.flush()?;
396            remove_file(&tempfile_path)?;
397            rename(&new_tempfile_path, &tempfile_path)?;
398        }
399
400        sleep(Duration::from_secs(1));
401
402        with_current_qiniu_config(|loaded| {
403            let config = loaded.and_then(|c| c.as_single()).unwrap();
404            assert_eq!(config.access_key(), "test-ak-5");
405            assert_eq!(config.secret_key(), "test-sk-5");
406            assert_eq!(config.bucket(), "test-bucket-5");
407        });
408
409        remove_file(&tempfile_path)?;
410
411        Ok(())
412    }
413
414    #[test]
415    fn test_set_config() -> Result<()> {
416        env_logger::try_init().ok();
417        let _defer = ResetFinally;
418
419        let mut config = ConfigBuilder::new(
420            "test-ak-1",
421            "test-sk-1",
422            "test-bucket-1",
423            Some(vec!["http://io1.com".into(), "http://io2.com".into()]),
424        )
425        .build();
426
427        set_qiniu_config(config.to_owned());
428
429        with_current_qiniu_config(|loaded| {
430            let config = loaded.and_then(|c| c.as_single()).unwrap();
431            assert_eq!(config.access_key(), "test-ak-1");
432            assert_eq!(config.secret_key(), "test-sk-1");
433            assert_eq!(config.bucket(), "test-bucket-1");
434        });
435
436        config.set_access_key("test-ak-2");
437        config.set_secret_key("test-sk-2");
438        config.set_bucket("test-bucket-2");
439        set_qiniu_config(config);
440
441        with_current_qiniu_config(|loaded| {
442            let config = loaded.and_then(|c| c.as_single()).unwrap();
443            assert_eq!(config.access_key(), "test-ak-2");
444            assert_eq!(config.secret_key(), "test-sk-2");
445            assert_eq!(config.bucket(), "test-bucket-2");
446        });
447
448        Ok(())
449    }
450
451    #[test]
452    fn test_load_multi_clusters_config() -> Result<()> {
453        env_logger::try_init().ok();
454        let _defer = ResetFinally;
455
456        let tempfile_path_1 = {
457            let config = ConfigBuilder::new(
458                "test-ak-1",
459                "test-sk-1",
460                "test-bucket-1",
461                Some(vec!["http://io-11.com".into(), "http://io-12.com".into()]),
462            )
463            .build();
464            let mut tempfile = TempFileBuilder::new()
465                .prefix("1-")
466                .suffix(".toml")
467                .tempfile()?;
468            tempfile.write_all(&toml::to_vec(&config)?)?;
469            tempfile.flush()?;
470            tempfile.into_temp_path()
471        };
472        let tempfile_path_2 = {
473            let config = ConfigBuilder::new(
474                "test-ak-2",
475                "test-sk-2",
476                "test-bucket-2",
477                Some(vec!["http://io-21.com".into(), "http://io-22.com".into()]),
478            )
479            .build();
480            let mut tempfile = TempFileBuilder::new()
481                .prefix("2-")
482                .suffix(".toml")
483                .tempfile()?;
484            tempfile.write_all(&toml::to_vec(&config)?)?;
485            tempfile.flush()?;
486            tempfile.into_temp_path()
487        };
488        let tempdir = tempdir()?;
489        let tempfile_path = {
490            let mut config = HashMap::with_capacity(2);
491            config.insert("config_1", tempfile_path_1.to_path_buf());
492            config.insert("config_2", tempfile_path_2.to_path_buf());
493            let mut tempfile = TempFileBuilder::new()
494                .prefix("all-")
495                .suffix(".toml")
496                .tempfile_in(tempdir.path())?;
497            tempfile.write_all(&toml::to_vec(&config)?)?;
498            tempfile.flush()?;
499            env::set_var(QINIU_MULTI_ENV, tempfile.path().as_os_str());
500            tempfile.into_temp_path()
501        };
502        let _env_guard = QiniuEnvGuard::new(tempfile_path.as_os_str());
503
504        with_current_qiniu_config_mut(|config| {
505            let multi_config = config.as_mut().unwrap().as_multi_mut().unwrap();
506            assert!(multi_config
507                .with_key("config_1", |config| {
508                    assert_eq!(config.access_key(), "test-ak-1");
509                })
510                .is_some());
511            assert!(multi_config
512                .with_key("config_2", |config| {
513                    assert_eq!(config.access_key(), "test-ak-2");
514                })
515                .is_some());
516            assert!(multi_config
517                .with_key("config_3", |config| {
518                    assert_eq!(config.access_key(), "test-ak-3");
519                })
520                .is_none());
521            multi_config.set_config_select_callback(|configs, key| match key {
522                "config_1" => configs.get("config_2"),
523                "config_2" => configs.get("config_1"),
524                "config_3" => configs.get("config_3"),
525                _ => None,
526            });
527            assert!(multi_config
528                .with_key("config_1", |config| {
529                    assert_eq!(config.access_key(), "test-ak-2");
530                })
531                .is_some());
532            assert!(multi_config
533                .with_key("config_2", |config| {
534                    assert_eq!(config.access_key(), "test-ak-1");
535                })
536                .is_some());
537            assert!(multi_config
538                .with_key("config_3", |config| {
539                    assert_eq!(config.access_key(), "test-ak-3");
540                })
541                .is_none());
542        });
543
544        {
545            let config = ConfigBuilder::new(
546                "test-ak-22",
547                "test-sk-22",
548                "test-bucket-22",
549                Some(vec!["http://io-21.com".into(), "http://io-22.com".into()]),
550            )
551            .build();
552            fs::write(&tempfile_path_2, toml::to_vec(&config)?)?;
553        }
554
555        sleep(Duration::from_secs(1));
556
557        with_current_qiniu_config_mut(|config| {
558            let multi_config = config.as_mut().unwrap().as_multi_mut().unwrap();
559            assert!(multi_config
560                .with_key("config_1", |config| {
561                    assert_eq!(config.access_key(), "test-ak-22");
562                })
563                .is_some());
564            assert!(multi_config
565                .with_key("config_2", |config| {
566                    assert_eq!(config.access_key(), "test-ak-1");
567                })
568                .is_some());
569            assert!(multi_config
570                .with_key("config_3", |config| {
571                    assert_eq!(config.access_key(), "test-ak-3");
572                })
573                .is_none());
574        });
575
576        let tempfile_path_3 = {
577            let config = ConfigBuilder::new(
578                "test-ak-3",
579                "test-sk-3",
580                "test-bucket-3",
581                Some(vec!["http://io-31.com".into(), "http://io-32.com".into()]),
582            )
583            .build();
584            let mut tempfile = TempFileBuilder::new()
585                .prefix("3-")
586                .suffix(".toml")
587                .tempfile()?;
588            tempfile.write_all(&toml::to_vec(&config)?)?;
589            tempfile.flush()?;
590            tempfile.into_temp_path()
591        };
592
593        {
594            let mut config = HashMap::with_capacity(3);
595            config.insert("config_1", tempfile_path_1.to_path_buf());
596            config.insert("config_2", tempfile_path_2.to_path_buf());
597            config.insert("config_3", tempfile_path_3.to_path_buf());
598            fs::write(&tempfile_path, toml::to_vec(&config)?)?;
599        };
600
601        sleep(Duration::from_secs(1));
602
603        {
604            let mut config = qiniu_config().write().unwrap();
605            let multi_config = config.as_mut().unwrap().as_multi_mut().unwrap();
606            assert!(multi_config
607                .with_key("config_1", |config| {
608                    assert_eq!(config.access_key(), "test-ak-22");
609                })
610                .is_some());
611            assert!(multi_config
612                .with_key("config_2", |config| {
613                    assert_eq!(config.access_key(), "test-ak-1");
614                })
615                .is_some());
616            assert!(multi_config
617                .with_key("config_3", |config| {
618                    assert_eq!(config.access_key(), "test-ak-3");
619                })
620                .is_some());
621        }
622
623        {
624            let config = ConfigBuilder::new(
625                "test-ak-32",
626                "test-sk-32",
627                "test-bucket-32",
628                Some(vec!["http://io-31.com".into(), "http://io-32.com".into()]),
629            )
630            .build();
631            fs::write(&tempfile_path_3, toml::to_vec(&config)?)?;
632        }
633
634        sleep(Duration::from_secs(1));
635
636        with_current_qiniu_config_mut(|config| {
637            let multi_config = config.as_mut().unwrap().as_multi_mut().unwrap();
638            assert!(multi_config
639                .with_key("config_1", |config| {
640                    assert_eq!(config.access_key(), "test-ak-22");
641                })
642                .is_some());
643            assert!(multi_config
644                .with_key("config_2", |config| {
645                    assert_eq!(config.access_key(), "test-ak-1");
646                })
647                .is_some());
648            assert!(multi_config
649                .with_key("config_3", |config| {
650                    assert_eq!(config.access_key(), "test-ak-32");
651                })
652                .is_some());
653        });
654
655        assert_eq!(watch_dirs_count(), 2);
656        assert_eq!(watch_files_count(), 4);
657
658        {
659            let mut config = HashMap::with_capacity(2);
660            config.insert("config_2", tempfile_path_2.to_path_buf());
661            config.insert("config_3", tempfile_path_3.to_path_buf());
662            fs::write(&tempfile_path, toml::to_vec(&config)?)?;
663        }
664
665        sleep(Duration::from_secs(1));
666
667        with_current_qiniu_config_mut(|config| {
668            let multi_config = config.as_mut().unwrap().as_multi_mut().unwrap();
669            assert!(multi_config
670                .with_key("config_1", |config| {
671                    assert_eq!(config.access_key(), "test-ak-22");
672                })
673                .is_some());
674            assert!(multi_config
675                .with_key("config_2", |config| {
676                    assert_eq!(config.access_key(), "test-ak-1");
677                })
678                .is_none());
679            assert!(multi_config
680                .with_key("config_3", |config| {
681                    assert_eq!(config.access_key(), "test-ak-32");
682                })
683                .is_some());
684        });
685
686        {
687            let config = ConfigBuilder::new(
688                "test-ak-12",
689                "test-sk-12",
690                "test-bucket-12",
691                Some(vec!["http://io-11.com".into(), "http://io-12.com".into()]),
692            )
693            .build();
694            fs::write(&tempfile_path_1, toml::to_vec(&config)?)?;
695        }
696
697        sleep(Duration::from_secs(1));
698
699        assert_eq!(watch_dirs_count(), 2);
700        assert_eq!(watch_files_count(), 3);
701
702        {
703            fs::write(
704                &tempfile_path,
705                toml::to_vec(&HashMap::<String, PathBuf>::new())?,
706            )?;
707        };
708
709        sleep(Duration::from_secs(1));
710
711        assert_eq!(watch_dirs_count(), 1);
712        assert_eq!(watch_files_count(), 1);
713
714        with_current_qiniu_config_mut(|config| {
715            *config = None;
716        });
717
718        assert_eq!(watch_dirs_count(), 0);
719        assert_eq!(watch_files_count(), 0);
720
721        with_current_qiniu_config_mut(|config| {
722            *config = Some(
723                MultipleClustersConfig::builder()
724                    .add_cluster(
725                        "config_1",
726                        ConfigBuilder::new(
727                            "test-ak-1",
728                            "test-sk-1",
729                            "test-bucket-1",
730                            Some(vec!["http://io-11.com".into(), "http://io-12.com".into()]),
731                        )
732                        .original_path(Some(tempfile_path_1.to_path_buf()))
733                        .build(),
734                    )
735                    .add_cluster(
736                        "config_2",
737                        ConfigBuilder::new(
738                            "test-ak-2",
739                            "test-sk-2",
740                            "test-bucket-2",
741                            Some(vec!["http://io-21.com".into(), "http://io-22.com".into()]),
742                        )
743                        .original_path(Some(tempfile_path_2.to_path_buf()))
744                        .build(),
745                    )
746                    .add_cluster(
747                        "config_3",
748                        ConfigBuilder::new(
749                            "test-ak-3",
750                            "test-sk-3",
751                            "test-bucket-3",
752                            Some(vec!["http://io-31.com".into(), "http://io-32.com".into()]),
753                        )
754                        .original_path(Some(tempfile_path_3.to_path_buf()))
755                        .build(),
756                    )
757                    .original_path(Some(tempfile_path.to_path_buf()))
758                    .build()
759                    .into(),
760            );
761        });
762
763        assert_eq!(watch_dirs_count(), 2);
764        assert_eq!(watch_files_count(), 4);
765
766        with_current_qiniu_config_mut(|config| {
767            *config = Some(
768                ConfigBuilder::new(
769                    "test-ak-1",
770                    "test-sk-1",
771                    "test-bucket-1",
772                    Some(vec!["http://io-11.com".into(), "http://io-12.com".into()]),
773                )
774                .original_path(Some(tempfile_path_1.to_path_buf()))
775                .build()
776                .into(),
777            );
778        });
779
780        assert_eq!(watch_dirs_count(), 1);
781        assert_eq!(watch_files_count(), 1);
782
783        unwatch_all()?;
784
785        Ok(())
786    }
787
788    #[test]
789    fn test_load_config_without_hot_reloading() -> Result<()> {
790        env_logger::try_init().ok();
791        let _defer = ResetFinally;
792
793        let _guard = QiniuHotReloadingEnvGuard::new();
794
795        let mut config = ConfigBuilder::new(
796            "test-ak-1",
797            "test-sk-1",
798            "test-bucket-1",
799            Some(vec!["http://io1.com".into(), "http://io2.com".into()]),
800        )
801        .build();
802        let tempfile_path = {
803            let mut tempfile = TempFileBuilder::new().suffix(".toml").tempfile()?;
804            tempfile.write_all(&toml::to_vec(&config)?)?;
805            tempfile.flush()?;
806            tempfile.into_temp_path()
807        };
808        let _env_guard = QiniuEnvGuard::new(tempfile_path.as_os_str());
809
810        with_current_qiniu_config(|loaded| {
811            let config = loaded.and_then(|c| c.as_single()).unwrap();
812            assert_eq!(config.access_key(), "test-ak-1");
813            assert_eq!(config.secret_key(), "test-sk-1");
814            assert_eq!(config.bucket(), "test-bucket-1");
815        });
816
817        sleep(Duration::from_secs(1));
818
819        config.set_access_key("test-ak-2");
820        config.set_secret_key("test-sk-2");
821        config.set_bucket("test-bucket-2");
822
823        {
824            let mut tempfile = OpenOptions::new()
825                .write(true)
826                .truncate(true)
827                .open(&tempfile_path)?;
828            tempfile.write_all(&toml::to_vec(&config)?)?;
829            tempfile.flush()?;
830        }
831
832        sleep(Duration::from_secs(1));
833
834        with_current_qiniu_config(|loaded| {
835            let config = loaded.and_then(|c| c.as_single()).unwrap();
836            assert_eq!(config.access_key(), "test-ak-1");
837            assert_eq!(config.secret_key(), "test-sk-1");
838            assert_eq!(config.bucket(), "test-bucket-1");
839        });
840
841        Ok(())
842    }
843
844    #[test]
845    fn test_default_select_callback_of_multi_clusters_config() -> Result<()> {
846        env_logger::try_init().ok();
847        let _defer = ResetFinally;
848
849        let tempfile_path_1 = {
850            let config = ConfigBuilder::new(
851                "test-ak-1",
852                "test-sk-1",
853                "test-bucket-1",
854                Some(vec!["http://io-11.com".into(), "http://io-12.com".into()]),
855            )
856            .build();
857            let mut tempfile = TempFileBuilder::new()
858                .prefix("1-")
859                .suffix(".toml")
860                .tempfile()?;
861            tempfile.write_all(&toml::to_vec(&config)?)?;
862            tempfile.flush()?;
863            tempfile.into_temp_path()
864        };
865        let tempfile_path_2 = {
866            let config = ConfigBuilder::new(
867                "test-ak-2",
868                "test-sk-2",
869                "test-bucket-2",
870                Some(vec!["http://io-21.com".into(), "http://io-22.com".into()]),
871            )
872            .build();
873            let mut tempfile = TempFileBuilder::new()
874                .prefix("2-")
875                .suffix(".toml")
876                .tempfile()?;
877            tempfile.write_all(&toml::to_vec(&config)?)?;
878            tempfile.flush()?;
879            tempfile.into_temp_path()
880        };
881        let tempfile_path = {
882            let mut config = HashMap::with_capacity(2);
883            config.insert("/node1", tempfile_path_1.to_path_buf());
884            config.insert("/node12", tempfile_path_2.to_path_buf());
885            let mut tempfile = TempFileBuilder::new()
886                .prefix("all-")
887                .suffix(".toml")
888                .tempfile()?;
889            tempfile.write_all(&toml::to_vec(&config)?)?;
890            tempfile.flush()?;
891            tempfile.into_temp_path()
892        };
893        let _env_guard = QiniuMultiEnvGuard::new(tempfile_path.as_os_str());
894
895        with_current_qiniu_config_mut(|config| {
896            let multi_config = config.as_mut().unwrap().as_multi_mut().unwrap();
897            assert!(multi_config
898                .with_key("/node1", |config| {
899                    assert_eq!(config.access_key(), "test-ak-1");
900                })
901                .is_some());
902            assert!(multi_config
903                .with_key("/node12", |config| {
904                    assert_eq!(config.access_key(), "test-ak-2");
905                })
906                .is_some());
907        });
908
909        Ok(())
910    }
911
912    #[test]
913    fn test_range_reader_from_multi_clusters_config() -> Result<()> {
914        env_logger::try_init().ok();
915        let _defer = ResetFinally;
916
917        let tempfile_path_1 = {
918            let config = ConfigBuilder::new(
919                "test-ak-1",
920                "test-sk-1",
921                "test-bucket-1",
922                Some(vec!["http://io-11.com".into(), "http://io-12.com".into()]),
923            )
924            .max_retry_concurrency(Some(1))
925            .build();
926            let mut tempfile = TempFileBuilder::new()
927                .prefix("1-")
928                .suffix(".toml")
929                .tempfile()?;
930            tempfile.write_all(&toml::to_vec(&config)?)?;
931            tempfile.flush()?;
932            tempfile.into_temp_path()
933        };
934        let tempfile_path_2 = {
935            let config = ConfigBuilder::new(
936                "test-ak-2",
937                "test-sk-2",
938                "test-bucket-2",
939                Some(vec!["http://io-21.com".into(), "http://io-22.com".into()]),
940            )
941            .max_retry_concurrency(Some(0))
942            .build();
943            let mut tempfile = TempFileBuilder::new()
944                .prefix("2-")
945                .suffix(".toml")
946                .tempfile()?;
947            tempfile.write_all(&toml::to_vec(&config)?)?;
948            tempfile.flush()?;
949            tempfile.into_temp_path()
950        };
951        let tempfile_path = {
952            let mut config = HashMap::with_capacity(2);
953            config.insert("/node1", tempfile_path_1.to_path_buf());
954            config.insert("/node2", tempfile_path_2.to_path_buf());
955            let mut tempfile = TempFileBuilder::new()
956                .prefix("all-")
957                .suffix(".toml")
958                .tempfile()?;
959            tempfile.write_all(&toml::to_vec(&config)?)?;
960            tempfile.flush()?;
961            tempfile.into_temp_path()
962        };
963        let _env_guard = QiniuMultiEnvGuard::new(tempfile_path.as_os_str());
964
965        {
966            let downloader = RangeReader::from_env("/node1/file1".to_owned()).unwrap();
967            assert_eq!(
968                downloader.io_urls(),
969                vec!["http://io-11.com".to_owned(), "http://io-12.com".to_owned()]
970            );
971            assert!(downloader.is_async());
972        }
973        {
974            let downloader = RangeReader::from_env("/node2/file1".to_owned()).unwrap();
975            assert_eq!(
976                downloader.io_urls(),
977                vec!["http://io-21.com".to_owned(), "http://io-22.com".to_owned()]
978            );
979            assert!(!downloader.is_async());
980        }
981        assert!(RangeReader::from_env("/node3/file1".to_owned()).is_none());
982
983        {
984            let config = ConfigBuilder::new(
985                "test-ak-1",
986                "test-sk-1",
987                "test-bucket-1",
988                Some(vec!["http://io-112.com".into(), "http://io-122.com".into()]),
989            )
990            .max_retry_concurrency(Some(0))
991            .build();
992            fs::write(&tempfile_path_1, toml::to_vec(&config)?)?;
993        }
994
995        sleep(Duration::from_secs(1));
996
997        {
998            let downloader = RangeReader::from_env("/node1/file1".to_owned()).unwrap();
999            assert_eq!(
1000                downloader.io_urls(),
1001                vec![
1002                    "http://io-112.com".to_owned(),
1003                    "http://io-122.com".to_owned()
1004                ]
1005            );
1006            assert!(!downloader.is_async());
1007        }
1008        {
1009            let downloader = RangeReader::from_env("/node2/file1".to_owned()).unwrap();
1010            assert_eq!(
1011                downloader.io_urls(),
1012                vec!["http://io-21.com".to_owned(), "http://io-22.com".to_owned()]
1013            );
1014            assert!(!downloader.is_async());
1015        }
1016
1017        {
1018            let mut config = HashMap::with_capacity(2);
1019            config.insert("/node1", tempfile_path_1.to_path_buf());
1020            fs::write(&tempfile_path, toml::to_vec(&config)?)?;
1021        }
1022
1023        sleep(Duration::from_secs(1));
1024
1025        {
1026            let downloader = RangeReader::from_env("/node1/file1".to_owned()).unwrap();
1027            assert_eq!(
1028                downloader.io_urls(),
1029                vec![
1030                    "http://io-112.com".to_owned(),
1031                    "http://io-122.com".to_owned()
1032                ]
1033            );
1034            assert!(!downloader.is_async());
1035        }
1036        assert!(RangeReader::from_env("/node2/file1".to_owned()).is_none());
1037
1038        Ok(())
1039    }
1040
1041    struct ResetFinally;
1042
1043    impl Drop for ResetFinally {
1044        fn drop(&mut self) {
1045            reset_static_vars();
1046            unwatch_all().unwrap();
1047        }
1048    }
1049
1050    struct QiniuHotReloadingEnvGuard;
1051
1052    impl QiniuHotReloadingEnvGuard {
1053        fn new() -> Self {
1054            env::set_var(QINIU_DISABLE_CONFIG_HOT_RELOADING_ENV, "1");
1055            Self
1056        }
1057    }
1058
1059    impl Drop for QiniuHotReloadingEnvGuard {
1060        fn drop(&mut self) {
1061            env::remove_var(QINIU_DISABLE_CONFIG_HOT_RELOADING_ENV)
1062        }
1063    }
1064
1065    struct QiniuEnvGuard;
1066
1067    impl QiniuEnvGuard {
1068        fn new(val: &OsStr) -> Self {
1069            env::set_var(QINIU_ENV, val);
1070            Self
1071        }
1072    }
1073
1074    impl Drop for QiniuEnvGuard {
1075        fn drop(&mut self) {
1076            env::remove_var(QINIU_ENV)
1077        }
1078    }
1079
1080    struct QiniuMultiEnvGuard;
1081
1082    impl QiniuMultiEnvGuard {
1083        fn new(val: &OsStr) -> Self {
1084            env::set_var(QINIU_MULTI_ENV, val);
1085            Self
1086        }
1087    }
1088
1089    impl Drop for QiniuMultiEnvGuard {
1090        fn drop(&mut self) {
1091            env::remove_var(QINIU_MULTI_ENV)
1092        }
1093    }
1094}