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/// # use opendal::Scheme;
52///
53/// # fn main() -> Result<()> {
54/// let _ = Operator::new(services::Memory::default())?
55///     .layer(ChaosLayer::new(0.1))
56///     .finish();
57/// Ok(())
58/// # }
59/// ```
60#[derive(Debug, Clone)]
61pub struct ChaosLayer {
62    error_ratio: f64,
63}
64
65impl ChaosLayer {
66    /// Create a new chaos layer with specified error ratio.
67    ///
68    /// # Panics
69    ///
70    /// Input error_ratio must in [0.0..=1.0]
71    pub fn new(error_ratio: f64) -> Self {
72        assert!(
73            (0.0..=1.0).contains(&error_ratio),
74            "error_ratio must between 0.0 and 1.0"
75        );
76        Self { error_ratio }
77    }
78}
79
80impl<A: Access> Layer<A> for ChaosLayer {
81    type LayeredAccess = ChaosAccessor<A>;
82
83    fn layer(&self, inner: A) -> Self::LayeredAccess {
84        ChaosAccessor {
85            inner,
86            rng: StdRng::from_entropy(),
87            error_ratio: self.error_ratio,
88        }
89    }
90}
91
92#[derive(Debug)]
93pub struct ChaosAccessor<A> {
94    inner: A,
95    rng: StdRng,
96
97    error_ratio: f64,
98}
99
100impl<A: Access> LayeredAccess for ChaosAccessor<A> {
101    type Inner = A;
102    type Reader = ChaosReader<A::Reader>;
103    type Writer = A::Writer;
104    type Lister = A::Lister;
105    type Deleter = A::Deleter;
106
107    fn inner(&self) -> &Self::Inner {
108        &self.inner
109    }
110
111    async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
112        self.inner
113            .read(path, args)
114            .await
115            .map(|(rp, r)| (rp, ChaosReader::new(r, self.rng.clone(), self.error_ratio)))
116    }
117
118    async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
119        self.inner.write(path, args).await
120    }
121
122    async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
123        self.inner.list(path, args).await
124    }
125
126    async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
127        self.inner.delete().await
128    }
129}
130
131/// ChaosReader will inject error into read operations.
132pub struct ChaosReader<R> {
133    inner: R,
134    rng: Arc<Mutex<StdRng>>,
135
136    error_ratio: f64,
137}
138
139impl<R> ChaosReader<R> {
140    fn new(inner: R, rng: StdRng, error_ratio: f64) -> Self {
141        Self {
142            inner,
143            rng: Arc::new(Mutex::new(rng)),
144            error_ratio,
145        }
146    }
147
148    /// If I feel lucky, we can return the correct response. Otherwise,
149    /// we need to generate an error.
150    fn i_feel_lucky(&self) -> bool {
151        let point = self.rng.lock().unwrap().gen_range(0..=100);
152        point >= (self.error_ratio * 100.0) as i32
153    }
154
155    fn unexpected_eof() -> Error {
156        Error::new(ErrorKind::Unexpected, "I am your chaos!")
157            .with_operation("chaos")
158            .set_temporary()
159    }
160}
161
162impl<R: oio::Read> oio::Read for ChaosReader<R> {
163    async fn read(&mut self) -> Result<Buffer> {
164        if self.i_feel_lucky() {
165            self.inner.read().await
166        } else {
167            Err(Self::unexpected_eof())
168        }
169    }
170}