sentinel_core/core/isolation/
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    /// Concurrency represents the concurrency of in-flight requests
15    Concurrency,
16}
17
18impl Default for MetricType {
19    fn default() -> MetricType {
20        MetricType::Concurrency
21    }
22}
23
24/// `Rule` describes the policy for system resiliency.
25#[cfg_attr(
26    feature = "ds_k8s",
27    derive(CustomResource, JsonSchema),
28    kube(
29        group = "rust.datasource.sentinel.io",
30        version = "v1alpha1",
31        kind = "IsolationResource",
32        namespaced
33    )
34)]
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(default)]
37pub struct Rule {
38    /// `id` represents the unique ID of the rule (optional).
39    pub id: String,
40    /// `resource` represents the target resource definition
41    pub resource: String,
42    /// `metric_type` indicates the type of the trigger metric.
43    pub metric_type: MetricType,
44    pub threshold: u32,
45}
46
47impl Default for Rule {
48    fn default() -> Self {
49        Rule {
50            #[cfg(target_arch = "wasm32")]
51            id: String::new(),
52            #[cfg(not(target_arch = "wasm32"))]
53            id: uuid::Uuid::new_v4().to_string(),
54            resource: String::default(),
55            metric_type: MetricType::default(),
56            threshold: 0,
57        }
58    }
59}
60
61impl PartialEq for Rule {
62    fn eq(&self, other: &Self) -> bool {
63        self.resource == other.resource
64            && self.metric_type == other.metric_type
65            && self.threshold == other.threshold
66    }
67}
68
69impl Eq for Rule {}
70
71impl Hash for Rule {
72    fn hash<H: Hasher>(&self, state: &mut H) {
73        self.id.hash(state);
74        self.resource.hash(state);
75    }
76}
77
78impl SentinelRule for Rule {
79    fn resource_name(&self) -> String {
80        format!("{:?}", self.metric_type)
81    }
82
83    fn is_valid(&self) -> crate::Result<()> {
84        if self.resource.len() == 0 {
85            return Err(Error::msg("empty resource of isolation rule"));
86        }
87
88        if self.threshold == 0 {
89            return Err(Error::msg("zero threshold"));
90        }
91        Ok(())
92    }
93}
94
95impl fmt::Display for Rule {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        let fmtted = serde_json::to_string_pretty(self).unwrap();
98        write!(f, "{}", fmtted)
99    }
100}
101
102#[cfg(test)]
103mod test {
104    use super::*;
105
106    #[test]
107    #[should_panic(expected = "zero threshold")]
108    fn invalid_threshold() {
109        let rule = Rule {
110            resource: "invalid_threshold".into(),
111            threshold: 0,
112            ..Default::default()
113        };
114        rule.is_valid().unwrap();
115    }
116
117    #[test]
118    #[should_panic(expected = "empty resource of isolation rule")]
119    fn invalid_cpu_usage() {
120        let rule = Rule {
121            threshold: 1,
122            ..Default::default()
123        };
124        rule.is_valid().unwrap();
125    }
126}