opendal/layers/chaos.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::sync::Arc;
19use std::sync::Mutex;
20
21use rand::prelude::*;
22use rand::rngs::StdRng;
23
24use crate::raw::*;
25use crate::*;
26
27/// Inject chaos into underlying services for robustness test.
28///
29/// # Chaos
30///
31/// Chaos tests is a part of stress test. By generating errors at specified
32/// error ratio, we can reproduce underlying services error more reliable.
33///
34/// Running tests under ChaosLayer will make your application more robust.
35///
36/// For example: If we specify an error rate of 0.5, there is a 50% chance
37/// of an EOF error for every read operation.
38///
39/// # Note
40///
41/// For now, ChaosLayer only injects read operations. More operations may
42/// be added in the future.
43///
44/// # Examples
45///
46/// ```no_run
47/// # use opendal::layers::ChaosLayer;
48/// # use opendal::services;
49/// # use opendal::Operator;
50/// # use opendal::Result;
51///
52/// # fn main() -> Result<()> {
53/// let _ = Operator::new(services::Memory::default())?
54/// .layer(ChaosLayer::new(0.1))
55/// .finish();
56/// Ok(())
57/// # }
58/// ```
59#[derive(Debug, Clone)]
60pub struct ChaosLayer {
61 error_ratio: f64,
62}
63
64impl ChaosLayer {
65 /// Create a new chaos layer with specified error ratio.
66 ///
67 /// # Panics
68 ///
69 /// Input error_ratio must in [0.0..=1.0]
70 pub fn new(error_ratio: f64) -> Self {
71 assert!(
72 (0.0..=1.0).contains(&error_ratio),
73 "error_ratio must between 0.0 and 1.0"
74 );
75 Self { error_ratio }
76 }
77}
78
79impl<A: Access> Layer<A> for ChaosLayer {
80 type LayeredAccess = ChaosAccessor<A>;
81
82 fn layer(&self, inner: A) -> Self::LayeredAccess {
83 ChaosAccessor {
84 inner,
85 rng: StdRng::from_entropy(),
86 error_ratio: self.error_ratio,
87 }
88 }
89}
90
91#[derive(Debug)]
92pub struct ChaosAccessor<A> {
93 inner: A,
94 rng: StdRng,
95
96 error_ratio: f64,
97}
98
99impl<A: Access> LayeredAccess for ChaosAccessor<A> {
100 type Inner = A;
101 type Reader = ChaosReader<A::Reader>;
102 type Writer = A::Writer;
103 type Lister = A::Lister;
104 type Deleter = A::Deleter;
105
106 fn inner(&self) -> &Self::Inner {
107 &self.inner
108 }
109
110 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
111 self.inner
112 .read(path, args)
113 .await
114 .map(|(rp, r)| (rp, ChaosReader::new(r, self.rng.clone(), self.error_ratio)))
115 }
116
117 async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
118 self.inner.write(path, args).await
119 }
120
121 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
122 self.inner.list(path, args).await
123 }
124
125 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
126 self.inner.delete().await
127 }
128}
129
130/// ChaosReader will inject error into read operations.
131pub struct ChaosReader<R> {
132 inner: R,
133 rng: Arc<Mutex<StdRng>>,
134
135 error_ratio: f64,
136}
137
138impl<R> ChaosReader<R> {
139 fn new(inner: R, rng: StdRng, error_ratio: f64) -> Self {
140 Self {
141 inner,
142 rng: Arc::new(Mutex::new(rng)),
143 error_ratio,
144 }
145 }
146
147 /// If I feel lucky, we can return the correct response. Otherwise,
148 /// we need to generate an error.
149 fn i_feel_lucky(&self) -> bool {
150 let point = self.rng.lock().unwrap().gen_range(0..=100);
151 point >= (self.error_ratio * 100.0) as i32
152 }
153
154 fn unexpected_eof() -> Error {
155 Error::new(ErrorKind::Unexpected, "I am your chaos!")
156 .with_operation("chaos")
157 .set_temporary()
158 }
159}
160
161impl<R: oio::Read> oio::Read for ChaosReader<R> {
162 async fn read(&mut self) -> Result<Buffer> {
163 if self.i_feel_lucky() {
164 self.inner.read().await
165 } else {
166 Err(Self::unexpected_eof())
167 }
168 }
169}