sentinel_core/core/system/
rule.rs

1use crate::{base::SentinelRule, Error};
2use serde::{Deserialize, Serialize};
3use serde_json;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6cfg_k8s! {
7    use schemars::JsonSchema;
8    use kube::CustomResource;
9}
10
11#[cfg_attr(feature = "ds_k8s", derive(JsonSchema))]
12#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Hash, Eq)]
13pub enum MetricType {
14    /// Load represents system load1 in Linux/Unix.
15    Load,
16    /// AvgRT represents the average response time of all inbound requests.
17    AvgRT,
18    /// Concurrency represents the concurrency of all inbound requests.
19    Concurrency,
20    /// InboundQPS represents the QPS of all inbound requests.
21    InboundQPS,
22    /// CpuUsage represents the CPU usage percentage of the system.
23    CpuUsage,
24}
25
26impl Default for MetricType {
27    fn default() -> MetricType {
28        MetricType::Load
29    }
30}
31
32#[cfg_attr(feature = "ds_k8s", derive(JsonSchema))]
33#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize, Eq)]
34pub enum AdaptiveStrategy {
35    NoAdaptive,
36    /// BBR represents the adaptive strategy based on ideas of TCP BBR.
37    BBR,
38}
39
40impl Default for AdaptiveStrategy {
41    fn default() -> AdaptiveStrategy {
42        AdaptiveStrategy::NoAdaptive
43    }
44}
45
46/// `Rule` describes the policy for system resiliency.
47#[cfg_attr(
48    feature = "ds_k8s",
49    derive(CustomResource, JsonSchema),
50    kube(
51        group = "rust.datasource.sentinel.io",
52        version = "v1alpha1",
53        kind = "SystemResource",
54        namespaced
55    )
56)]
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(default)]
59pub struct Rule {
60    /// `id` represents the unique ID of the rule (optional).
61    pub id: String,
62    /// `metric_type` indicates the type of the trigger metric.
63    pub metric_type: MetricType,
64    /// `threshold` represents the lower bound trigger of the adaptive strategy.
65    /// Adaptive strategies will not be activated until target metric has reached the trigger count.
66    pub threshold: f64,
67    /// `strategy` represents the adaptive strategy.
68    pub strategy: AdaptiveStrategy,
69}
70
71impl Default for Rule {
72    fn default() -> Self {
73        Rule {
74            #[cfg(target_arch = "wasm32")]
75            id: String::new(),
76            #[cfg(not(target_arch = "wasm32"))]
77            id: uuid::Uuid::new_v4().to_string(),
78            metric_type: MetricType::default(),
79            threshold: 0.0,
80            strategy: AdaptiveStrategy::default(),
81        }
82    }
83}
84
85impl PartialEq for Rule {
86    fn eq(&self, other: &Self) -> bool {
87        self.metric_type == other.metric_type
88            && self.threshold == other.threshold
89            && self.strategy == other.strategy
90    }
91}
92
93impl SentinelRule for Rule {
94    fn resource_name(&self) -> String {
95        format!("{:?}", self.metric_type)
96    }
97
98    fn is_valid(&self) -> crate::Result<()> {
99        if self.threshold < 0.0 {
100            return Err(Error::msg("negative threshold"));
101        }
102        if self.metric_type == MetricType::CpuUsage
103            && (self.threshold > 100.0 || self.threshold < 0.0)
104        {
105            return Err(Error::msg("invalid CPU usage, valid range is [0.0, 100.0]"));
106        }
107        if self.metric_type == MetricType::Load && (self.threshold > 1.0 || self.threshold < 0.0) {
108            return Err(Error::msg(
109                "invalid average load, valid range is [0.0, 1.0]",
110            ));
111        }
112        Ok(())
113    }
114}
115
116impl Hash for Rule {
117    fn hash<H: Hasher>(&self, state: &mut H) {
118        self.id.hash(state);
119        self.metric_type.hash(state);
120    }
121}
122
123impl Eq for Rule {}
124
125impl fmt::Display for Rule {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        let fmtted = serde_json::to_string_pretty(self).unwrap();
128        write!(f, "{}", fmtted)
129    }
130}
131
132#[cfg(test)]
133mod test {
134    use super::*;
135
136    #[test]
137    #[should_panic(expected = "negative threshold")]
138    fn invalid_threshold() {
139        let rule = Rule {
140            metric_type: MetricType::InboundQPS,
141            threshold: -1.0,
142            ..Default::default()
143        };
144        rule.is_valid().unwrap();
145    }
146
147    #[test]
148    #[should_panic(expected = "invalid CPU usage, valid range is [0.0, 100.0]")]
149    fn invalid_cpu_usage() {
150        let rule = Rule {
151            metric_type: MetricType::CpuUsage,
152            threshold: 115.0,
153            ..Default::default()
154        };
155        rule.is_valid().unwrap();
156    }
157}