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#[inline]
28pub fn is_qiniu_enabled() -> bool {
29 with_current_qiniu_config(|config| config.is_some())
30}
31
32#[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#[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#[inline]
55pub fn set_qiniu_config(config: Config) {
56 set_config_and_reload(config.into(), false)
57}
58
59#[inline]
61pub fn set_qiniu_single_cluster_config(config: SingleClusterConfig) {
62 set_qiniu_config(config)
63}
64
65#[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#[derive(Error, Debug)]
156#[non_exhaustive]
157pub enum ClustersConfigParseError {
158 #[error("Parse config as json error: {0}")]
160 JSONError(#[from] serde_json::Error),
161
162 #[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}