1use std::collections::HashSet;
16use std::sync::Arc;
17
18use common_meta::kv_backend::KvBackendRef;
19use common_meta::kv_backend::txn::{Compare, CompareOp, Txn, TxnOp};
20use common_meta::rpc::store::RangeRequest;
21use serde::{Deserialize, Serialize};
22use snafu::ResultExt;
23
24use crate::error::{self, Result};
25use crate::key::{NodeSchedulingKey, NodeSchedulingRole};
26
27pub type NodeSchedulingManagerRef = Arc<NodeSchedulingManager>;
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct NodeSchedulingValue {
35 pub schedulable: bool,
37}
38
39pub struct NodeSchedulingManager {
44 kv_backend: KvBackendRef,
45}
46
47impl NodeSchedulingManager {
48 pub fn new(kv_backend: KvBackendRef) -> Self {
49 Self { kv_backend }
50 }
51
52 pub async fn mark_unschedulable(&self, role: NodeSchedulingRole, node_id: u64) -> Result<bool> {
56 let key: Vec<u8> = NodeSchedulingKey::new(role, node_id).into();
57 let value = serde_json::to_vec(&NodeSchedulingValue { schedulable: false }).context(
58 error::SerializeToJsonSnafu {
59 input: String::from_utf8_lossy(&key).into_owned(),
60 },
61 )?;
62
63 let txn = match self
64 .kv_backend
65 .get(&key)
66 .await
67 .context(error::KvBackendSnafu)?
68 {
69 None => Txn::put_if_not_exists(key, value),
70 Some(kv) if kv.value == value => return Ok(false),
71 Some(kv) => Txn::compare_and_put(key, kv.value, value),
72 };
73 let response = self
74 .kv_backend
75 .txn(txn)
76 .await
77 .context(error::KvBackendSnafu)?;
78 Ok(response.succeeded)
79 }
80
81 pub async fn mark_schedulable(&self, role: NodeSchedulingRole, node_id: u64) -> Result<bool> {
86 let key: Vec<u8> = NodeSchedulingKey::new(role, node_id).into();
87 let txn = Txn::new()
89 .when(vec![Compare::new(key.clone(), CompareOp::NotEqual, None)])
90 .and_then(vec![TxnOp::Delete(key)]);
91 let response = self
92 .kv_backend
93 .txn(txn)
94 .await
95 .context(error::KvBackendSnafu)?;
96 Ok(response.succeeded)
97 }
98
99 pub async fn is_schedulable(&self, role: NodeSchedulingRole, node_id: u64) -> Result<bool> {
101 let key = NodeSchedulingKey::new(role, node_id);
102 Ok(self
103 .get_value(&key)
104 .await?
105 .is_none_or(|value| value.schedulable))
106 }
107
108 pub async fn list_unschedulable_node_ids(
112 &self,
113 role: NodeSchedulingRole,
114 ) -> Result<HashSet<u64>> {
115 let request = RangeRequest::new().with_prefix(NodeSchedulingKey::prefix_key(role));
116 let response = self
117 .kv_backend
118 .range(request)
119 .await
120 .context(error::KvBackendSnafu)?;
121
122 let mut node_ids = HashSet::new();
123 for kv in response.kvs {
124 let key = NodeSchedulingKey::try_from(kv.key)?;
125 if !parse_value(&kv.value)?.schedulable {
126 node_ids.insert(key.node_id);
127 }
128 }
129 Ok(node_ids)
130 }
131
132 async fn get_value(&self, key: &NodeSchedulingKey) -> Result<Option<NodeSchedulingValue>> {
133 let key: Vec<u8> = (*key).into();
134 let Some(kv) = self
135 .kv_backend
136 .get(&key)
137 .await
138 .context(error::KvBackendSnafu)?
139 else {
140 return Ok(None);
141 };
142
143 parse_value(&kv.value).map(Some)
144 }
145}
146
147fn parse_value(value: &[u8]) -> Result<NodeSchedulingValue> {
148 serde_json::from_slice(value).context(error::DeserializeFromJsonSnafu {
149 input_len: value.len(),
150 })
151}
152
153#[cfg(test)]
154mod tests {
155 use std::sync::Arc;
156
157 use common_meta::kv_backend::KvBackend;
158 use common_meta::kv_backend::memory::MemoryKvBackend;
159
160 use super::*;
161 use crate::metasrv::SelectorContext;
162
163 fn new_manager() -> (
164 Arc<MemoryKvBackend<common_meta::error::Error>>,
165 NodeSchedulingManager,
166 ) {
167 let kv_backend = Arc::new(MemoryKvBackend::new());
168 let manager = NodeSchedulingManager::new(kv_backend.clone());
169 (kv_backend, manager)
170 }
171
172 #[tokio::test]
173 async fn test_mark_unschedulable_is_idempotent() {
174 let (kv_backend, manager) = new_manager();
175
176 assert!(
177 manager
178 .is_schedulable(NodeSchedulingRole::Datanode, 1)
179 .await
180 .unwrap()
181 );
182 assert!(
183 manager
184 .mark_unschedulable(NodeSchedulingRole::Datanode, 1)
185 .await
186 .unwrap()
187 );
188 assert!(
189 !manager
190 .mark_unschedulable(NodeSchedulingRole::Datanode, 1)
191 .await
192 .unwrap()
193 );
194
195 assert!(
196 !manager
197 .is_schedulable(NodeSchedulingRole::Datanode, 1)
198 .await
199 .unwrap()
200 );
201 assert_eq!(
202 HashSet::from([1]),
203 manager
204 .list_unschedulable_node_ids(NodeSchedulingRole::Datanode)
205 .await
206 .unwrap()
207 );
208
209 let key: Vec<u8> = NodeSchedulingKey::new(NodeSchedulingRole::Datanode, 1).into();
210 let kv = kv_backend.get(&key).await.unwrap().unwrap();
211 assert_eq!(br#"{"schedulable":false}"#, kv.value.as_slice());
212
213 assert!(
214 manager
215 .mark_schedulable(NodeSchedulingRole::Datanode, 1)
216 .await
217 .unwrap()
218 );
219 assert!(
220 !manager
221 .mark_schedulable(NodeSchedulingRole::Datanode, 1)
222 .await
223 .unwrap()
224 );
225
226 assert!(
227 manager
228 .is_schedulable(NodeSchedulingRole::Datanode, 1)
229 .await
230 .unwrap()
231 );
232 assert!(
233 manager
234 .list_unschedulable_node_ids(NodeSchedulingRole::Datanode)
235 .await
236 .unwrap()
237 .is_empty()
238 );
239 }
240
241 #[tokio::test]
242 async fn test_mark_schedulable_without_state_writes_nothing() {
243 let (kv_backend, manager) = new_manager();
244
245 assert!(
246 !manager
247 .mark_schedulable(NodeSchedulingRole::Datanode, 2)
248 .await
249 .unwrap()
250 );
251
252 let key: Vec<u8> = NodeSchedulingKey::new(NodeSchedulingRole::Datanode, 2).into();
253 assert!(kv_backend.get(&key).await.unwrap().is_none());
254 }
255
256 #[tokio::test]
257 async fn test_scheduling_state_is_role_scoped() {
258 let (_, manager) = new_manager();
259
260 manager
261 .mark_unschedulable(NodeSchedulingRole::Datanode, 7)
262 .await
263 .unwrap();
264
265 assert!(
266 manager
267 .is_schedulable(NodeSchedulingRole::Flownode, 7)
268 .await
269 .unwrap()
270 );
271 assert!(
272 manager
273 .list_unschedulable_node_ids(NodeSchedulingRole::Flownode)
274 .await
275 .unwrap()
276 .is_empty()
277 );
278 }
279
280 #[tokio::test]
281 async fn test_scheduling_state_is_visible_to_other_manager() {
282 let (kv_backend, manager_a) = new_manager();
283 let manager_b = NodeSchedulingManager::new(kv_backend);
284
285 manager_a
286 .mark_unschedulable(NodeSchedulingRole::Datanode, 3)
287 .await
288 .unwrap();
289
290 assert!(
291 !manager_b
292 .is_schedulable(NodeSchedulingRole::Datanode, 3)
293 .await
294 .unwrap()
295 );
296 }
297
298 #[tokio::test]
299 async fn test_concurrent_mark_unschedulable_changes_once() {
300 let (_, manager) = new_manager();
301
302 let (a, b) = tokio::join!(
303 manager.mark_unschedulable(NodeSchedulingRole::Datanode, 2),
304 manager.mark_unschedulable(NodeSchedulingRole::Datanode, 2)
305 );
306 let changed = [a.unwrap(), b.unwrap()];
307 assert_eq!(1, changed.iter().filter(|changed| **changed).count());
308 }
309
310 async fn new_selector_context() -> SelectorContext {
313 use common_meta::peer::Peer;
314
315 use crate::test_util::{
316 create_meta_peer_client, new_node_scheduling_discovery, put_datanodes,
317 };
318
319 let client = create_meta_peer_client();
320 put_datanodes(
321 &client,
322 vec![Peer::empty(1), Peer::empty(2), Peer::empty(3)],
323 )
324 .await;
325
326 let (manager, discovery) = new_node_scheduling_discovery(&client);
327 manager
328 .mark_unschedulable(NodeSchedulingRole::Datanode, 2)
329 .await
330 .unwrap();
331
332 SelectorContext {
333 affinity_fallback: Default::default(),
334 affinity: Arc::new(common_meta::affinity::NoopAffinity),
335 peer_discovery: Arc::new(discovery),
336 }
337 }
338
339 #[tokio::test]
340 async fn test_lease_based_selector_skips_unschedulable() {
341 use crate::error;
342 use crate::selector::lease_based::LeaseBasedSelector;
343 use crate::selector::{Selector, SelectorOptions};
344
345 let ctx = new_selector_context().await;
346 let selector = LeaseBasedSelector;
347
348 let peers = selector
349 .select(
350 &ctx,
351 SelectorOptions {
352 min_required_items: 2,
353 ..Default::default()
354 },
355 )
356 .await
357 .unwrap();
358 assert_eq!(2, peers.len());
359 for peer in peers {
360 assert!([1, 3].contains(&peer.id), "unexpected peer: {peer:?}");
361 }
362
363 let err = selector
364 .select(
365 &ctx,
366 SelectorOptions {
367 min_required_items: 3,
368 ..Default::default()
369 },
370 )
371 .await
372 .unwrap_err();
373 assert!(
374 matches!(err, error::Error::NoEnoughAvailableNode { .. }),
375 "unexpected error: {err:?}"
376 );
377 }
378
379 #[tokio::test]
380 async fn test_round_robin_selector_skips_unschedulable() {
381 use crate::metasrv::SelectTarget;
382 use crate::selector::round_robin::RoundRobinSelector;
383 use crate::selector::{Selector, SelectorOptions};
384
385 let ctx = new_selector_context().await;
386 let selector = RoundRobinSelector::new(SelectTarget::Datanode);
387
388 let peers = selector
389 .select(
390 &ctx,
391 SelectorOptions {
392 min_required_items: 2,
393 ..Default::default()
394 },
395 )
396 .await
397 .unwrap();
398 let ids = peers.iter().map(|peer| peer.id).collect::<Vec<_>>();
399 assert_eq!(vec![1, 3], ids);
400 }
401}