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}