1use std::collections::{HashMap, VecDeque};
12
13use crate::communication::{Channel, Message, MessageProcessingError};
14use crate::node_set::NodeSet;
15use crate::topology::{TopologyError, TopologyTree};
16
17use thiserror::Error;
18
19#[derive(Error, Debug)]
25pub enum RouteResolvingError {
26 #[error("Invalid root or gateway node: {0}")]
28 InvalidRoot(String),
29
30 #[error("No route available to {0}")]
32 NoRoute(String),
33
34 #[error("Invalid destination: {0}, host is unreachable")]
36 Unreachable(String),
37
38 #[error("Invalid resolution request: {0} -> {1}")]
40 SameSourceDest(String, String),
41
42 #[error("Topology error: {0}")]
44 Topology(#[from] TopologyError),
45
46 #[error("NodeSet error: {0}")]
48 NodeSet(#[from] crate::node_set::NodeSetError),
49}
50
51#[derive(Error, Debug)]
53pub enum RecvError {
54 #[error("{0}")]
56 Message(#[from] MessageProcessingError),
57
58 #[error("{0}")]
63 Gateway(String),
64}
65
66#[derive(Debug, Clone)]
72pub struct RouteEntry {
73 network: NodeSet,
75 nexthops: NodeSet,
77}
78
79#[derive(Debug)]
89pub struct PropagationTreeRouter {
90 root: String,
92 fanout: usize,
94 nodes_fanin: HashMap<String, usize>,
96 table: Vec<RouteEntry>,
98 unreachable_hosts: NodeSet,
100}
101
102impl PropagationTreeRouter {
103 pub fn new(
112 root: &str,
113 topology: &TopologyTree,
114 fanout: usize,
115 ) -> Result<Self, RouteResolvingError> {
116 let mut router = Self {
117 root: root.to_string(),
118 fanout,
119 nodes_fanin: HashMap::new(),
120 table: Vec::new(),
121 unreachable_hosts: NodeSet::new(),
122 };
123 router.table_generate(root, topology)?;
124 Ok(router)
125 }
126
127 fn table_generate(
133 &mut self,
134 root: &str,
135 topology: &TopologyTree,
136 ) -> Result<(), RouteResolvingError> {
137 let _root_group = topology
139 .find_nodegroup(root)
140 .map_err(|_| RouteResolvingError::InvalidRoot(root.to_string()))?;
141
142 self.table.clear();
143
144 let root_tng = topology
148 .root()
149 .ok_or_else(|| RouteResolvingError::InvalidRoot(root.to_string()))?;
150
151 let subtree_root = Self::find_group_containing(root_tng, root)
155 .ok_or_else(|| RouteResolvingError::InvalidRoot(root.to_string()))?;
156
157 for child in subtree_root.children() {
158 let mut network = NodeSet::new();
159 Self::collect_reachable(child, &mut network);
160 self.table.push(RouteEntry {
161 network,
162 nexthops: child.nodeset.clone(),
163 });
164 }
165
166 Ok(())
167 }
168
169 fn find_group_containing<'a>(
171 group: &'a crate::topology::TopologyNodeGroup,
172 node: &str,
173 ) -> Option<&'a crate::topology::TopologyNodeGroup> {
174 if group.nodeset.contains(node) {
175 return Some(group);
176 }
177 for child in group.children() {
178 if let Some(found) = Self::find_group_containing(child, node) {
179 return Some(found);
180 }
181 }
182 None
183 }
184
185 fn collect_reachable(group: &crate::topology::TopologyNodeGroup, dest: &mut NodeSet) {
188 for child in group.children() {
189 dest.update(&child.nodeset);
190 Self::collect_reachable(child, dest);
191 }
192 }
193
194 pub fn dispatch(&self, dst: &NodeSet) -> Vec<(NodeSet, NodeSet)> {
200 let mut remaining = dst.clone();
201 let mut results = Vec::new();
202
203 for entry in &self.table {
205 let dst_inter = entry.network.intersection(&remaining);
206 if !dst_inter.is_empty() {
207 remaining.difference_update(&dst_inter);
208 for host_name in dst_inter.iter() {
210 if let Ok(hop) = self.next_hop_str(&host_name) {
211 let mut hop_ns = NodeSet::new();
212 let _ = hop_ns.update_str(&hop);
213 let mut host_ns = NodeSet::new();
214 let _ = host_ns.update_str(&host_name);
215 results.push((hop_ns, host_ns));
216 }
217 }
218 }
219 }
220
221 if !remaining.is_empty() {
223 results.push((remaining.clone(), remaining));
224 }
225
226 results
227 }
228
229 pub fn next_hop(&mut self, dst: &NodeSet) -> Result<NodeSet, RouteResolvingError> {
234 let dst_str = dst.to_string();
235
236 if !self.unreachable_hosts.intersection(dst).is_empty() {
238 return Err(RouteResolvingError::Unreachable(dst_str));
239 }
240
241 if self.root == dst_str {
243 return Err(RouteResolvingError::SameSourceDest(
244 self.root.clone(),
245 dst_str,
246 ));
247 }
248
249 for entry in &self.table {
250 if !entry.network.intersection(dst).is_empty() {
252 let res = self.best_next_hop(&entry.nexthops)?;
253 let res_str = res.to_string();
254 *self.nodes_fanin.entry(res_str).or_insert(0) += dst.len();
255 return Ok(res);
256 }
257 if !entry.nexthops.intersection(dst).is_empty() {
259 return Ok(dst.clone());
260 }
261 }
262
263 Err(RouteResolvingError::NoRoute(dst_str))
264 }
265
266 fn next_hop_str(&self, dst: &str) -> Result<String, RouteResolvingError> {
268 if self.unreachable_hosts.contains(dst) {
270 return Err(RouteResolvingError::Unreachable(dst.to_string()));
271 }
272
273 if self.root == dst {
274 return Err(RouteResolvingError::SameSourceDest(
275 self.root.clone(),
276 dst.to_string(),
277 ));
278 }
279
280 let dst_ns = NodeSet::parse(dst)?;
281
282 for entry in &self.table {
283 if !entry.network.intersection(&dst_ns).is_empty() {
284 let res = self.best_next_hop(&entry.nexthops)?;
285 return Ok(res.to_string());
286 }
287 if !entry.nexthops.intersection(&dst_ns).is_empty() {
288 return Ok(dst.to_string());
289 }
290 }
291
292 Err(RouteResolvingError::NoRoute(dst.to_string()))
293 }
294
295 pub fn mark_unreachable(&mut self, dst: &str) {
297 let _ = self.unreachable_hosts.update_str(dst);
298 }
299
300 fn best_next_hop(&self, candidates: &NodeSet) -> Result<NodeSet, RouteResolvingError> {
304 let available = candidates.difference(&self.unreachable_hosts);
305 if available.is_empty() {
306 return Err(RouteResolvingError::NoRoute(
307 "all candidates unreachable".into(),
308 ));
309 }
310
311 let mut best: Option<String> = None;
312 let mut best_connections = usize::MAX;
313
314 for host in available.iter() {
315 let connections = self.nodes_fanin.get(&host).copied().unwrap_or(0);
316 if connections < best_connections {
317 best_connections = connections;
318 best = Some(host);
319 }
320 }
321
322 match best {
323 Some(host) => {
324 let mut ns = NodeSet::new();
325 let _ = ns.update_str(&host);
326 Ok(ns)
327 }
328 None => Err(RouteResolvingError::NoRoute(
329 "no candidate available".into(),
330 )),
331 }
332 }
333
334 pub fn root(&self) -> &str {
336 &self.root
337 }
338
339 pub fn fanout(&self) -> usize {
341 self.fanout
342 }
343
344 pub fn table(&self) -> &[RouteEntry] {
346 &self.table
347 }
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
356pub enum ChannelState {
357 Init,
359 Config,
361 Control,
363 Gather,
365 Closed,
367}
368
369#[derive(Debug)]
379pub struct PropagationChannel {
380 pub channel: Channel,
382 pub gateway: String,
384 pub state: ChannelState,
386 send_queue: VecDeque<(Message, Vec<String>)>,
390 pub pickup_events: Vec<String>,
395 pub last_rc: Option<i32>,
397 write_history: VecDeque<(u64, String, usize)>,
400 pub results: Vec<PropagationResult>,
402}
403
404#[derive(Debug, Clone)]
406pub enum PropagationResult {
407 StdOut { node: String, data: Vec<u8> },
409 StdErr { node: String, data: Vec<u8> },
411 Retcode { node: String, rc: i32 },
413 Timeout { node: String },
415 Routing {
417 event: String,
418 gateway: String,
419 targets: String,
420 },
421}
422
423fn split_lines(buf: &[u8]) -> Vec<Vec<u8>> {
428 let mut lines = Vec::new();
429 let mut start = 0;
430 let mut i = 0;
431 while i < buf.len() {
432 if buf[i] == b'\n' || buf[i] == b'\r' {
433 lines.push(buf[start..i].to_vec());
434 if buf[i] == b'\r' && i + 1 < buf.len() && buf[i + 1] == b'\n' {
435 i += 1;
436 }
437 i += 1;
438 start = i;
439 } else {
440 i += 1;
441 }
442 }
443 if start < buf.len() {
444 lines.push(buf[start..].to_vec());
445 }
446 lines
447}
448
449impl PropagationChannel {
450 pub fn new(gateway: &str) -> Self {
452 Self {
453 channel: Channel::new(true), gateway: gateway.to_string(),
455 state: ChannelState::Init,
456 send_queue: VecDeque::new(),
457 pickup_events: Vec::new(),
458 last_rc: None,
459 write_history: VecDeque::new(),
460 results: Vec::new(),
461 }
462 }
463
464 pub fn start(&mut self, topology_bytes: &[u8]) {
468 self.channel.opened = true;
470
471 let mut cfg = Message::configuration(&self.gateway);
473 cfg.data_encode(topology_bytes);
474 self.channel.send(&cfg);
475 self.state = ChannelState::Config;
476 }
477
478 fn send_ctl(&mut self, ctl: Message, pickup_nodes: Vec<String>) {
486 self.channel.send(&ctl);
487 self.pickup_events.extend(pickup_nodes);
488 }
489
490 pub fn send_queued(&mut self, msg: Message) {
495 self.send_queued_pickup(msg, Vec::new());
496 }
497
498 fn send_queued_pickup(&mut self, msg: Message, pickup_nodes: Vec<String>) {
502 if self.channel.setup && self.send_queue.is_empty() {
503 self.send_ctl(msg, pickup_nodes);
505 } else {
506 self.send_queue.push_back((msg, pickup_nodes));
507 }
508 }
509
510 fn send_dequeue(&mut self) {
513 if let Some((msg, pickup_nodes)) = self.send_queue.pop_front() {
514 self.send_ctl(msg, pickup_nodes);
515 }
516 }
517
518 pub fn shell(&mut self, nodes: &str, command: &[u8], worker_id: u64) {
524 let mut ctl = Message::control(worker_id);
525 if let Message::Control {
526 ref mut action,
527 ref mut target,
528 ..
529 } = ctl
530 {
531 *action = "shell".to_string();
532 *target = nodes.to_string();
533 }
534 ctl.data_encode(command);
535 let pickup_nodes = NodeSet::parse(nodes)
538 .map(|ns| ns.iter().collect())
539 .unwrap_or_else(|_| vec![nodes.to_string()]);
540 self.send_queued_pickup(ctl, pickup_nodes);
541 }
542
543 pub fn write(&mut self, nodes: &str, buf: &[u8], worker_id: u64) {
545 let mut ctl = Message::control(worker_id);
546 if let Message::Control {
547 ref mut action,
548 ref mut target,
549 ..
550 } = ctl
551 {
552 *action = "write".to_string();
553 *target = nodes.to_string();
554 }
555 let msgid = ctl.msgid();
556 ctl.data_encode(buf);
557 self.write_history
558 .push_back((msgid, nodes.to_string(), buf.len()));
559 self.send_queued(ctl);
560 }
561
562 pub fn set_write_eof(&mut self, nodes: &str, worker_id: u64) {
564 let mut ctl = Message::control(worker_id);
565 if let Message::Control {
566 ref mut action,
567 ref mut target,
568 ..
569 } = ctl
570 {
571 *action = "eof".to_string();
572 *target = nodes.to_string();
573 }
574 self.send_queued(ctl);
575 }
576
577 fn report_stderr(&self, nodeset: &NodeSet, buf: &[u8]) -> Vec<PropagationResult> {
584 let mut terminated = buf.to_vec();
585 terminated.push(b'\n');
586 let mut results = Vec::new();
587 for line in split_lines(&terminated) {
588 for node in nodeset.iter() {
589 results.push(PropagationResult::StdErr {
590 node,
591 data: line.clone(),
592 });
593 }
594 }
595 results
596 }
597
598 pub fn recv(&mut self, msg: Message) -> Result<Vec<PropagationResult>, RecvError> {
602 let mut new_results = Vec::new();
603
604 if msg.ident() == "END" {
606 self.state = ChannelState::Closed;
607 self.channel.close();
608 return Ok(new_results);
609 }
610
611 if msg.ident() == "SER" && msg.srcid() == Some(0) {
616 let decoded = msg.data_decode()?;
617 let nodeset = NodeSet::parse(msg.nodes().unwrap_or_default()).unwrap_or_default();
618 let results = self.report_stderr(&nodeset, &decoded);
619 self.results.extend(results.clone());
620 return Ok(results);
621 }
622
623 if msg.ident() == "CHA" {
625 self.channel.opened = true;
626 return Ok(new_results);
627 }
628
629 match self.state {
630 ChannelState::Config => {
631 match msg.ident() {
633 "ACK" => {
634 self.channel.setup = true;
635 self.state = ChannelState::Gather;
636 self.send_dequeue();
637 }
638 "ERR" => {
639 if let Message::Error { reason, .. } = &msg {
643 let gw = NodeSet::parse(&self.gateway).unwrap_or_default();
644 let results = self.report_stderr(&gw, reason.as_bytes());
645 self.results.extend(results.clone());
646 return Ok(results);
647 }
648 }
649 _ => {}
650 }
651 }
652 ChannelState::Control | ChannelState::Gather => {
653 match msg.ident() {
654 "ACK" => {
655 if let Message::Ack { ack, .. } = &msg {
657 if let Some(back) = self.write_history.back() {
658 if back.0 == *ack {
659 let _ = self.write_history.pop_back();
660 }
661 }
662 }
663 self.send_dequeue();
664 }
665 "OUT" => {
666 if let Some(nodes_str) = msg.nodes() {
667 if let Ok(decoded) = msg.data_decode() {
668 let ns = NodeSet::parse(nodes_str).unwrap_or_default();
669 for node in ns.iter() {
670 let result = PropagationResult::StdOut {
671 node,
672 data: decoded.clone(),
673 };
674 new_results.push(result);
675 }
676 }
677 }
678 }
679 "SER" => {
680 if let Some(nodes_str) = msg.nodes() {
683 if let Ok(decoded) = msg.data_decode() {
684 let ns = NodeSet::parse(nodes_str).unwrap_or_default();
685 new_results.extend(self.report_stderr(&ns, &decoded));
686 }
687 }
688 }
689 "RET" => {
690 if let Message::Retcode { retcode, nodes, .. } = &msg {
691 let ns = NodeSet::parse(nodes).unwrap_or_default();
692 for node in ns.iter() {
693 new_results.push(PropagationResult::Retcode { node, rc: *retcode });
694 }
695 }
696 }
697 "TIM" => {
698 if let Some(nodes_str) = msg.nodes() {
699 let ns = NodeSet::parse(nodes_str).unwrap_or_default();
700 for node in ns.iter() {
701 new_results.push(PropagationResult::Timeout { node });
702 }
703 }
704 }
705 "RTR" => {
706 if let Message::Routing {
707 event,
708 gateway,
709 targets,
710 ..
711 } = &msg
712 {
713 new_results.push(PropagationResult::Routing {
714 event: event.clone(),
715 gateway: gateway.clone(),
716 targets: targets.clone(),
717 });
718 }
719 }
720 "ERR" => {
721 if let Message::Error { reason, .. } = &msg {
725 self.channel.close();
726 self.state = ChannelState::Closed;
727 return Err(RecvError::Gateway(format!(
728 "{}: {}",
729 self.gateway, reason
730 )));
731 }
732 }
733 _ => {
734 }
736 }
737 }
738 _ => {}
739 }
740
741 self.results.extend(new_results.clone());
742 Ok(new_results)
743 }
744
745 pub fn ev_hup(&mut self, rc: i32) {
747 self.last_rc = Some(rc);
748 }
749
750 pub fn ev_close(&mut self) {
752 self.state = ChannelState::Closed;
753 }
754
755 pub fn current_state(&self) -> ChannelState {
757 self.state
758 }
759
760 pub fn is_gathering(&self) -> bool {
762 self.state == ChannelState::Gather
763 }
764
765 pub fn is_closed(&self) -> bool {
767 self.state == ChannelState::Closed
768 }
769}
770
771#[cfg(test)]
776mod tests {
777 use super::*;
778 use crate::topology::{TopologyGraph, TopologyTree};
779
780 fn build_test_topology() -> TopologyTree {
783 let mut graph = TopologyGraph::new();
784 let admin = NodeSet::parse("admin").unwrap();
785 let gw1 = NodeSet::parse("gw1").unwrap();
786 let gw2 = NodeSet::parse("gw2").unwrap();
787 let nodes1 = NodeSet::parse("node[1-4]").unwrap();
788 let nodes2 = NodeSet::parse("node[5-8]").unwrap();
789
790 graph.add_route(admin.clone(), gw1.clone()).unwrap();
791 graph.add_route(admin, gw2.clone()).unwrap();
792 graph.add_route(gw1, nodes1).unwrap();
793 graph.add_route(gw2, nodes2).unwrap();
794 graph.to_tree("admin").unwrap()
795 }
796
797 #[test]
800 fn test_error_display() {
801 let err = RouteResolvingError::InvalidRoot("badnode".into());
802 assert!(err.to_string().contains("badnode"));
803
804 let err = RouteResolvingError::NoRoute("node99".into());
805 assert!(err.to_string().contains("node99"));
806
807 let err = RouteResolvingError::Unreachable("gw1".into());
808 assert!(err.to_string().contains("gw1"));
809
810 let err = RouteResolvingError::SameSourceDest("a".into(), "a".into());
811 assert!(err.to_string().contains("a"));
812 }
813
814 #[test]
817 fn test_router_creation() {
818 let tree = build_test_topology();
819 let router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
820 assert_eq!(router.root(), "admin");
821 assert_eq!(router.fanout(), 0);
822 }
823
824 #[test]
825 fn test_router_invalid_root() {
826 let tree = build_test_topology();
827 let result = PropagationTreeRouter::new("nonexistent", &tree, 0);
828 assert!(result.is_err());
829 }
830
831 #[test]
832 fn test_router_next_hop() {
833 let tree = build_test_topology();
834 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
835
836 let dst = NodeSet::parse("node1").unwrap();
838 let hop = router.next_hop(&dst).unwrap();
839 assert_eq!(hop.to_string(), "gw1");
840
841 let dst = NodeSet::parse("node5").unwrap();
843 let hop = router.next_hop(&dst).unwrap();
844 assert_eq!(hop.to_string(), "gw2");
845 }
846
847 #[test]
848 fn test_router_same_source_dest() {
849 let tree = build_test_topology();
850 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
851 let dst = NodeSet::parse("admin").unwrap();
852 let result = router.next_hop(&dst);
853 assert!(result.is_err());
854 }
855
856 #[test]
857 fn test_router_mark_unreachable() {
858 let tree = build_test_topology();
859 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
860
861 router.mark_unreachable("gw1");
862
863 let dst = NodeSet::parse("node1").unwrap();
866 let result = router.next_hop(&dst);
867 assert!(result.is_err());
868 }
869
870 #[test]
871 fn test_router_dispatch_directly_connected() {
872 let tree = build_test_topology();
873 let router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
874
875 let dst = NodeSet::parse("localnode").unwrap();
877 let results = router.dispatch(&dst);
878 assert_eq!(results.len(), 1);
879 assert_eq!(results[0].0.to_string(), "localnode");
880 }
881
882 #[test]
883 fn test_router_dispatch_remote() {
884 let tree = build_test_topology();
885 let router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
886
887 let dst = NodeSet::parse("node[1-2]").unwrap();
888 let results = router.dispatch(&dst);
889 assert!(!results.is_empty());
891 for (hop, _target) in &results {
892 assert_eq!(hop.to_string(), "gw1");
893 }
894 }
895
896 #[test]
897 fn test_router_load_balancing() {
898 let mut graph = TopologyGraph::new();
901 let admin = NodeSet::parse("admin").unwrap();
902 let gws = NodeSet::parse("gw[1-2]").unwrap();
903 let nodes = NodeSet::parse("node[1-8]").unwrap();
904 graph.add_route(admin, gws).unwrap();
905
906 let gw1 = NodeSet::parse("gw1").unwrap();
908 let gw2 = NodeSet::parse("gw2").unwrap();
909 graph
910 .add_route(gw1, NodeSet::parse("node[1-4]").unwrap())
911 .unwrap();
912 graph
913 .add_route(gw2, NodeSet::parse("node[5-8]").unwrap())
914 .unwrap();
915
916 let tree = graph.to_tree("admin").unwrap();
917 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
918
919 let d1 = NodeSet::parse("node1").unwrap();
921 let _h1 = router.next_hop(&d1).unwrap();
922 let d2 = NodeSet::parse("node5").unwrap();
923 let _h2 = router.next_hop(&d2).unwrap();
924
925 }
928
929 #[test]
932 fn test_channel_creation() {
933 let ch = PropagationChannel::new("gw1");
934 assert_eq!(ch.gateway, "gw1");
935 assert_eq!(ch.current_state(), ChannelState::Init);
936 assert!(!ch.is_gathering());
937 assert!(!ch.is_closed());
938 }
939
940 #[test]
941 fn test_channel_start() {
942 let mut ch = PropagationChannel::new("gw1");
943 ch.start(b"topology data");
944 assert_eq!(ch.current_state(), ChannelState::Config);
945 assert!(ch.channel.opened);
946
947 let outgoing = ch.channel.take_outgoing().unwrap();
949 let xml = String::from_utf8(outgoing).unwrap();
950 assert!(xml.contains("type=\"CFG\""));
951 assert!(xml.contains("gateway=\"gw1\""));
952 }
953
954 #[test]
955 fn test_channel_config_ack() {
956 let mut ch = PropagationChannel::new("gw1");
957 ch.start(b"topo");
958 assert_eq!(ch.state, ChannelState::Config);
959
960 let ack = Message::ack(0);
962 let results = ch.recv(ack).unwrap();
963 assert!(results.is_empty());
964 assert_eq!(ch.state, ChannelState::Gather);
965 assert!(ch.channel.setup);
966 }
967
968 #[test]
969 fn test_channel_shell_command() {
970 let mut ch = PropagationChannel::new("gw1");
971 ch.start(b"topo");
972
973 ch.recv(Message::ack(0)).unwrap();
975
976 ch.shell("node[1-4]", b"uname -a", 42);
978
979 let outgoing = ch.channel.take_outgoing().unwrap();
981 let outgoing2 = ch.channel.take_outgoing().unwrap();
983 let xml = String::from_utf8(outgoing2).unwrap();
984 assert!(xml.contains("type=\"CTL\""));
985 assert!(xml.contains("action=\"shell\""));
986 assert!(xml.contains("target=\"node[1-4]\""));
987 }
988
989 #[test]
990 fn test_channel_recv_stdout() {
991 let mut ch = PropagationChannel::new("gw1");
992 ch.start(b"topo");
993 ch.recv(Message::ack(0)).unwrap();
994
995 let mut stdout = Message::stdout("node[1-2]", 1);
997 stdout.data_encode(b"hello world");
998 let results = ch.recv(stdout).unwrap();
999
1000 assert_eq!(results.len(), 2);
1001 for r in &results {
1002 if let PropagationResult::StdOut { data, .. } = r {
1003 assert_eq!(data, b"hello world");
1004 } else {
1005 panic!("expected StdOut");
1006 }
1007 }
1008 }
1009
1010 #[test]
1011 fn test_channel_recv_stderr() {
1012 let mut ch = PropagationChannel::new("gw1");
1013 ch.start(b"topo");
1014 ch.recv(Message::ack(0)).unwrap();
1015
1016 let mut stderr = Message::stderr("node1", 1);
1017 stderr.data_encode(b"error output");
1018 let results = ch.recv(stderr).unwrap();
1019
1020 assert_eq!(results.len(), 1);
1021 if let PropagationResult::StdErr { node, data } = &results[0] {
1022 assert_eq!(node, "node1");
1023 assert_eq!(data, b"error output");
1024 } else {
1025 panic!("expected StdErr");
1026 }
1027 }
1028
1029 #[test]
1030 fn test_channel_recv_retcode() {
1031 let mut ch = PropagationChannel::new("gw1");
1032 ch.start(b"topo");
1033 ch.recv(Message::ack(0)).unwrap();
1034
1035 let ret = Message::retcode("node[1-3]", 0, 1);
1036 let results = ch.recv(ret).unwrap();
1037
1038 assert_eq!(results.len(), 3);
1039 for r in &results {
1040 if let PropagationResult::Retcode { rc, .. } = r {
1041 assert_eq!(*rc, 0);
1042 } else {
1043 panic!("expected Retcode");
1044 }
1045 }
1046 }
1047
1048 #[test]
1049 fn test_channel_recv_timeout() {
1050 let mut ch = PropagationChannel::new("gw1");
1051 ch.start(b"topo");
1052 ch.recv(Message::ack(0)).unwrap();
1053
1054 let tim = Message::timeout("node[5-6]", 1);
1055 let results = ch.recv(tim).unwrap();
1056
1057 assert_eq!(results.len(), 2);
1058 for r in &results {
1059 assert!(matches!(r, PropagationResult::Timeout { .. }));
1060 }
1061 }
1062
1063 #[test]
1064 fn test_channel_recv_routing() {
1065 let mut ch = PropagationChannel::new("gw1");
1066 ch.start(b"topo");
1067 ch.recv(Message::ack(0)).unwrap();
1068
1069 let rtr = Message::routing("connect", "gw2", "node[10-20]", 1);
1070 let results = ch.recv(rtr).unwrap();
1071
1072 assert_eq!(results.len(), 1);
1073 if let PropagationResult::Routing {
1074 event,
1075 gateway,
1076 targets,
1077 } = &results[0]
1078 {
1079 assert_eq!(event, "connect");
1080 assert_eq!(gateway, "gw2");
1081 assert_eq!(targets, "node[10-20]");
1082 } else {
1083 panic!("expected Routing");
1084 }
1085 }
1086
1087 #[test]
1088 fn test_channel_recv_error() {
1089 let mut ch = PropagationChannel::new("gw1");
1090 ch.start(b"topo");
1091 ch.recv(Message::ack(0)).unwrap();
1092
1093 let err = Message::error("something went wrong");
1094 let result = ch.recv(err);
1095 assert!(result.is_err());
1096 }
1097
1098 #[test]
1099 fn test_channel_recv_end() {
1100 let mut ch = PropagationChannel::new("gw1");
1101 ch.start(b"topo");
1102 ch.recv(Message::ack(0)).unwrap();
1103
1104 let end = Message::end();
1105 ch.recv(end).unwrap();
1106 assert!(ch.is_closed());
1107 }
1108
1109 #[test]
1110 fn test_channel_ev_hup() {
1111 let mut ch = PropagationChannel::new("gw1");
1112 ch.ev_hup(1);
1113 assert_eq!(ch.last_rc, Some(1));
1114 }
1115
1116 #[test]
1117 fn test_channel_ev_close() {
1118 let mut ch = PropagationChannel::new("gw1");
1119 ch.ev_close();
1120 assert!(ch.is_closed());
1121 }
1122
1123 #[test]
1124 fn test_channel_send_queue() {
1125 let mut ch = PropagationChannel::new("gw1");
1126 ch.start(b"topo");
1127 ch.shell("node1", b"echo hi", 1);
1129
1130 let _cfg = ch.channel.take_outgoing().unwrap();
1132 assert!(ch.channel.take_outgoing().is_none());
1133
1134 ch.recv(Message::ack(0)).unwrap();
1136 let ctl = ch.channel.take_outgoing().unwrap();
1137 let xml = String::from_utf8(ctl).unwrap();
1138 assert!(xml.contains("type=\"CTL\""));
1139 }
1140
1141 #[test]
1142 fn test_channel_write_and_eof() {
1143 let mut ch = PropagationChannel::new("gw1");
1144 ch.start(b"topo");
1145 ch.recv(Message::ack(0)).unwrap();
1146
1147 ch.write("node1", b"input data", 1);
1148 ch.set_write_eof("node1", 1);
1149
1150 let _ = ch.channel.take_outgoing(); let write_msg = ch.channel.take_outgoing().unwrap();
1152 let xml = String::from_utf8(write_msg).unwrap();
1153 assert!(xml.contains("action=\"write\""));
1154
1155 let eof_msg = ch.channel.take_outgoing().unwrap();
1156 let xml2 = String::from_utf8(eof_msg).unwrap();
1157 assert!(xml2.contains("action=\"eof\""));
1158 }
1159
1160 #[test]
1161 fn test_channel_results_accumulate() {
1162 let mut ch = PropagationChannel::new("gw1");
1163 ch.start(b"topo");
1164 ch.recv(Message::ack(0)).unwrap();
1165
1166 let mut out1 = Message::stdout("node1", 1);
1168 out1.data_encode(b"line1");
1169 ch.recv(out1).unwrap();
1170
1171 let mut out2 = Message::stdout("node2", 1);
1172 out2.data_encode(b"line2");
1173 ch.recv(out2).unwrap();
1174
1175 let ret = Message::retcode("node[1-2]", 0, 1);
1176 ch.recv(ret).unwrap();
1177
1178 assert_eq!(ch.results.len(), 4);
1180 }
1181
1182 #[test]
1188 fn test_head_channel_gw_error_before_setup() {
1189 let mut ch = PropagationChannel::new("gw1");
1190 ch.start(b"topo");
1191 assert_eq!(ch.state, ChannelState::Config);
1192
1193 let err =
1194 Message::error("Message CFG has an invalid payload (unsupported pickle protocol: 5)");
1195 let results = ch.recv(err).unwrap();
1196
1197 assert_eq!(results.len(), 1);
1198 match &results[0] {
1199 PropagationResult::StdErr { node, data } => {
1200 assert_eq!(node, "gw1");
1201 assert_eq!(
1202 data,
1203 b"Message CFG has an invalid payload (unsupported pickle protocol: 5)"
1204 );
1205 }
1206 other => panic!("expected StdErr, got {:?}", other),
1207 }
1208 assert_eq!(ch.state, ChannelState::Config);
1210 }
1211
1212 #[test]
1215 fn test_head_channel_gw_error_empty_reason() {
1216 let mut ch = PropagationChannel::new("gw1");
1217 ch.start(b"topo");
1218
1219 let results = ch.recv(Message::error("")).unwrap();
1220 assert_eq!(results.len(), 1);
1221 match &results[0] {
1222 PropagationResult::StdErr { node, data } => {
1223 assert_eq!(node, "gw1");
1224 assert_eq!(data, b"");
1225 }
1226 other => panic!("expected StdErr, got {:?}", other),
1227 }
1228 }
1229
1230 #[test]
1234 fn test_head_channel_gw_error_after_setup() {
1235 let mut ch = PropagationChannel::new("gw1");
1236 ch.start(b"topo");
1237 ch.recv(Message::ack(0)).unwrap();
1238 assert!(ch.channel.setup);
1239
1240 let result = ch.recv(Message::error("bad news"));
1241 match result.unwrap_err() {
1242 RecvError::Gateway(text) => assert_eq!(text, "gw1: bad news"),
1243 other => panic!("expected Gateway error, got {:?}", other),
1244 }
1245 assert!(ch.results.is_empty());
1247 assert!(ch.channel.closed);
1249 }
1250
1251 #[test]
1256 fn test_head_channel_err_invalid_tag_reported_as_stderr() {
1257 let mut ch = PropagationChannel::new("gw1");
1258 let msgs = ch.channel.feed("<channel version=\"1.10.1\">").unwrap();
1260 for m in msgs {
1261 ch.recv(m).unwrap();
1262 }
1263 assert!(ch.channel.opened);
1264
1265 let err = ch.channel.feed("<foo></foo>").unwrap_err();
1268 let synthetic = ch
1269 .channel
1270 .handle_channel_error("gw1", &err)
1271 .expect("initiator must produce a synthetic stderr message");
1272 assert!(ch.channel.closed);
1273
1274 let results = ch.recv(synthetic).unwrap();
1275 assert_eq!(results.len(), 1);
1276 match &results[0] {
1277 PropagationResult::StdErr { node, data } => {
1278 assert_eq!(node, "gw1");
1279 assert_eq!(data, b"Invalid starting tag foo");
1280 }
1281 other => panic!("expected StdErr, got {:?}", other),
1282 }
1283 }
1284
1285 #[test]
1289 fn test_head_channel_err_parse_error_reported_as_stderr() {
1290 let mut ch = PropagationChannel::new("gw1");
1291 let msgs = ch.channel.feed("<channel version=\"1.10.1\">").unwrap();
1292 for m in msgs {
1293 ch.recv(m).unwrap();
1294 }
1295
1296 let err = ch
1297 .channel
1298 .feed("<message type=\"ABC\"</message>")
1299 .unwrap_err();
1300 let synthetic = ch
1301 .channel
1302 .handle_channel_error("gw1", &err)
1303 .expect("initiator must produce a synthetic stderr message");
1304 assert!(ch.channel.closed);
1305
1306 let results = ch.recv(synthetic).unwrap();
1307 assert_eq!(results.len(), 1);
1308 match &results[0] {
1309 PropagationResult::StdErr { node, data } => {
1310 assert_eq!(node, "gw1");
1311 assert!(
1312 String::from_utf8_lossy(data).contains("not well-formed"),
1313 "expected 'not well-formed' in {:?}",
1314 data
1315 );
1316 }
1317 other => panic!("expected StdErr, got {:?}", other),
1318 }
1319 }
1320
1321 #[test]
1324 fn test_channel_recv_nonrouted_stderr_srcid0() {
1325 let mut ch = PropagationChannel::new("gw1");
1326 ch.start(b"topo");
1327 ch.recv(Message::ack(0)).unwrap();
1328
1329 let mut ser = Message::stderr("node[1-2]", 0);
1330 ser.data_encode(b"line a\nline b");
1331 let results = ch.recv(ser).unwrap();
1332
1333 let collected: Vec<(&str, &[u8])> = results
1335 .iter()
1336 .map(|r| match r {
1337 PropagationResult::StdErr { node, data } => (node.as_str(), data.as_slice()),
1338 other => panic!("expected StdErr, got {:?}", other),
1339 })
1340 .collect();
1341 assert_eq!(
1342 collected,
1343 vec![
1344 ("node1", b"line a".as_slice()),
1345 ("node2", b"line a".as_slice()),
1346 ("node1", b"line b".as_slice()),
1347 ("node2", b"line b".as_slice()),
1348 ]
1349 );
1350 }
1351
1352 #[test]
1355 fn test_channel_recv_routed_stderr_per_line() {
1356 let mut ch = PropagationChannel::new("gw1");
1357 ch.start(b"topo");
1358 ch.recv(Message::ack(0)).unwrap();
1359
1360 let mut ser = Message::stderr("node1", 7);
1361 ser.data_encode(b"first\nsecond\n");
1362 let results = ch.recv(ser).unwrap();
1363
1364 let datas: Vec<&[u8]> = results
1367 .iter()
1368 .map(|r| match r {
1369 PropagationResult::StdErr { data, .. } => data.as_slice(),
1370 other => panic!("expected StdErr, got {:?}", other),
1371 })
1372 .collect();
1373 assert_eq!(
1374 datas,
1375 vec![b"first".as_slice(), b"second".as_slice(), b"".as_slice()]
1376 );
1377 }
1378
1379 #[test]
1382 fn test_report_stderr_empty_buffer_yields_empty_line() {
1383 let ch = PropagationChannel::new("gw1");
1384 let ns = NodeSet::parse("node1").unwrap();
1385 let results = ch.report_stderr(&ns, b"");
1386 assert_eq!(results.len(), 1);
1387 match &results[0] {
1388 PropagationResult::StdErr { node, data } => {
1389 assert_eq!(node, "node1");
1390 assert_eq!(data, b"");
1391 }
1392 other => panic!("expected StdErr, got {:?}", other),
1393 }
1394 }
1395
1396 #[test]
1398 fn test_split_lines_boundaries() {
1399 assert_eq!(
1400 split_lines(b"a\r\nb\rc\n"),
1401 vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()]
1402 );
1403 assert_eq!(split_lines(b"no-newline"), vec![b"no-newline".to_vec()]);
1404 assert_eq!(split_lines(b"\n"), vec![b"".to_vec()]);
1405 assert!(split_lines(b"").is_empty());
1406 }
1407
1408 #[test]
1412 fn test_send_ctl_pickup_fires_on_actual_send() {
1413 let mut ch = PropagationChannel::new("gw1");
1414 ch.start(b"topo");
1415
1416 ch.shell("node[1-2]", b"echo hi", 1);
1418 assert!(ch.pickup_events.is_empty());
1419
1420 ch.recv(Message::ack(0)).unwrap();
1422 assert_eq!(
1423 ch.pickup_events,
1424 vec!["node1".to_string(), "node2".to_string()]
1425 );
1426
1427 ch.shell("node3", b"echo hi", 1);
1429 assert_eq!(
1430 ch.pickup_events,
1431 vec![
1432 "node1".to_string(),
1433 "node2".to_string(),
1434 "node3".to_string()
1435 ]
1436 );
1437 }
1438
1439 #[test]
1442 fn test_write_and_eof_no_pickup() {
1443 let mut ch = PropagationChannel::new("gw1");
1444 ch.start(b"topo");
1445 ch.recv(Message::ack(0)).unwrap();
1446
1447 ch.write("node1", b"input", 1);
1448 ch.set_write_eof("node1", 1);
1449 assert!(ch.pickup_events.is_empty());
1450 }
1451
1452 #[test]
1455 fn test_send_queue_fifo_with_pickup() {
1456 let mut ch = PropagationChannel::new("gw1");
1457 ch.start(b"topo");
1458
1459 ch.shell("node1", b"cmd1", 1);
1460 ch.shell("node2", b"cmd2", 2);
1461
1462 let _cfg = ch.channel.take_outgoing().unwrap();
1463 assert!(ch.channel.take_outgoing().is_none());
1464
1465 ch.recv(Message::ack(0)).unwrap();
1466 let first = String::from_utf8(ch.channel.take_outgoing().unwrap()).unwrap();
1467 assert!(first.contains("target=\"node1\""));
1468 assert_eq!(ch.pickup_events, vec!["node1".to_string()]);
1470 }
1471}