sentinel_core/core/system/
rule.rs1use 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,
16 AvgRT,
18 Concurrency,
20 InboundQPS,
22 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,
38}
39
40impl Default for AdaptiveStrategy {
41 fn default() -> AdaptiveStrategy {
42 AdaptiveStrategy::NoAdaptive
43 }
44}
45
46#[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 pub id: String,
62 pub metric_type: MetricType,
64 pub threshold: f64,
67 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}