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(Debug, Clone)]
57pub struct RouteEntry {
58 network: NodeSet,
60 nexthops: NodeSet,
62}
63
64#[derive(Debug)]
74pub struct PropagationTreeRouter {
75 root: String,
77 fanout: usize,
79 nodes_fanin: HashMap<String, usize>,
81 table: Vec<RouteEntry>,
83 unreachable_hosts: NodeSet,
85}
86
87impl PropagationTreeRouter {
88 pub fn new(
97 root: &str,
98 topology: &TopologyTree,
99 fanout: usize,
100 ) -> Result<Self, RouteResolvingError> {
101 let mut router = Self {
102 root: root.to_string(),
103 fanout,
104 nodes_fanin: HashMap::new(),
105 table: Vec::new(),
106 unreachable_hosts: NodeSet::new(),
107 };
108 router.table_generate(root, topology)?;
109 Ok(router)
110 }
111
112 fn table_generate(
118 &mut self,
119 root: &str,
120 topology: &TopologyTree,
121 ) -> Result<(), RouteResolvingError> {
122 let _root_group = topology
124 .find_nodegroup(root)
125 .map_err(|_| RouteResolvingError::InvalidRoot(root.to_string()))?;
126
127 self.table.clear();
128
129 let root_tng = topology
133 .root()
134 .ok_or_else(|| RouteResolvingError::InvalidRoot(root.to_string()))?;
135
136 let subtree_root = Self::find_group_containing(root_tng, root)
140 .ok_or_else(|| RouteResolvingError::InvalidRoot(root.to_string()))?;
141
142 for child in subtree_root.children() {
143 let mut network = NodeSet::new();
144 Self::collect_reachable(child, &mut network);
145 self.table.push(RouteEntry {
146 network,
147 nexthops: child.nodeset.clone(),
148 });
149 }
150
151 Ok(())
152 }
153
154 fn find_group_containing<'a>(
156 group: &'a crate::topology::TopologyNodeGroup,
157 node: &str,
158 ) -> Option<&'a crate::topology::TopologyNodeGroup> {
159 if group.nodeset.contains(node) {
160 return Some(group);
161 }
162 for child in group.children() {
163 if let Some(found) = Self::find_group_containing(child, node) {
164 return Some(found);
165 }
166 }
167 None
168 }
169
170 fn collect_reachable(group: &crate::topology::TopologyNodeGroup, dest: &mut NodeSet) {
173 for child in group.children() {
174 dest.update(&child.nodeset);
175 Self::collect_reachable(child, dest);
176 }
177 }
178
179 pub fn dispatch(&self, dst: &NodeSet) -> Vec<(NodeSet, NodeSet)> {
185 let mut remaining = dst.clone();
186 let mut results = Vec::new();
187
188 for entry in &self.table {
190 let dst_inter = entry.network.intersection(&remaining);
191 if !dst_inter.is_empty() {
192 remaining.difference_update(&dst_inter);
193 for host_name in dst_inter.iter() {
195 if let Ok(hop) = self.next_hop_str(&host_name) {
196 let mut hop_ns = NodeSet::new();
197 let _ = hop_ns.update_str(&hop);
198 let mut host_ns = NodeSet::new();
199 let _ = host_ns.update_str(&host_name);
200 results.push((hop_ns, host_ns));
201 }
202 }
203 }
204 }
205
206 if !remaining.is_empty() {
208 results.push((remaining.clone(), remaining));
209 }
210
211 results
212 }
213
214 pub fn next_hop(&mut self, dst: &NodeSet) -> Result<NodeSet, RouteResolvingError> {
219 let dst_str = dst.to_string();
220
221 if !self.unreachable_hosts.intersection(dst).is_empty() {
223 return Err(RouteResolvingError::Unreachable(dst_str));
224 }
225
226 if self.root == dst_str {
228 return Err(RouteResolvingError::SameSourceDest(
229 self.root.clone(),
230 dst_str,
231 ));
232 }
233
234 for entry in &self.table {
235 if !entry.network.intersection(dst).is_empty() {
237 let res = self.best_next_hop(&entry.nexthops)?;
238 let res_str = res.to_string();
239 *self.nodes_fanin.entry(res_str).or_insert(0) += dst.len();
240 return Ok(res);
241 }
242 if !entry.nexthops.intersection(dst).is_empty() {
244 return Ok(dst.clone());
245 }
246 }
247
248 Err(RouteResolvingError::NoRoute(dst_str))
249 }
250
251 fn next_hop_str(&self, dst: &str) -> Result<String, RouteResolvingError> {
253 if self.unreachable_hosts.contains(dst) {
255 return Err(RouteResolvingError::Unreachable(dst.to_string()));
256 }
257
258 if self.root == dst {
259 return Err(RouteResolvingError::SameSourceDest(
260 self.root.clone(),
261 dst.to_string(),
262 ));
263 }
264
265 let dst_ns = NodeSet::parse(dst)?;
266
267 for entry in &self.table {
268 if !entry.network.intersection(&dst_ns).is_empty() {
269 let res = self.best_next_hop(&entry.nexthops)?;
270 return Ok(res.to_string());
271 }
272 if !entry.nexthops.intersection(&dst_ns).is_empty() {
273 return Ok(dst.to_string());
274 }
275 }
276
277 Err(RouteResolvingError::NoRoute(dst.to_string()))
278 }
279
280 pub fn mark_unreachable(&mut self, dst: &str) {
282 let _ = self.unreachable_hosts.update_str(dst);
283 }
284
285 fn best_next_hop(&self, candidates: &NodeSet) -> Result<NodeSet, RouteResolvingError> {
289 let available = candidates.difference(&self.unreachable_hosts);
290 if available.is_empty() {
291 return Err(RouteResolvingError::NoRoute(
292 "all candidates unreachable".into(),
293 ));
294 }
295
296 let mut best: Option<String> = None;
297 let mut best_connections = usize::MAX;
298
299 for host in available.iter() {
300 let connections = self.nodes_fanin.get(&host).copied().unwrap_or(0);
301 if connections < best_connections {
302 best_connections = connections;
303 best = Some(host);
304 }
305 }
306
307 match best {
308 Some(host) => {
309 let mut ns = NodeSet::new();
310 let _ = ns.update_str(&host);
311 Ok(ns)
312 }
313 None => Err(RouteResolvingError::NoRoute(
314 "no candidate available".into(),
315 )),
316 }
317 }
318
319 pub fn root(&self) -> &str {
321 &self.root
322 }
323
324 pub fn fanout(&self) -> usize {
326 self.fanout
327 }
328
329 pub fn table(&self) -> &[RouteEntry] {
331 &self.table
332 }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub enum ChannelState {
342 Init,
344 Config,
346 Control,
348 Gather,
350 Closed,
352}
353
354#[derive(Debug)]
364pub struct PropagationChannel {
365 pub channel: Channel,
367 pub gateway: String,
369 pub state: ChannelState,
371 send_queue: VecDeque<Message>,
373 pub last_rc: Option<i32>,
375 write_history: VecDeque<(u64, String, usize)>,
378 pub results: Vec<PropagationResult>,
380}
381
382#[derive(Debug, Clone)]
384pub enum PropagationResult {
385 StdOut { node: String, data: Vec<u8> },
387 StdErr { node: String, data: Vec<u8> },
389 Retcode { node: String, rc: i32 },
391 Timeout { node: String },
393 Routing {
395 event: String,
396 gateway: String,
397 targets: String,
398 },
399}
400
401impl PropagationChannel {
402 pub fn new(gateway: &str) -> Self {
404 Self {
405 channel: Channel::new(true), gateway: gateway.to_string(),
407 state: ChannelState::Init,
408 send_queue: VecDeque::new(),
409 last_rc: None,
410 write_history: VecDeque::new(),
411 results: Vec::new(),
412 }
413 }
414
415 pub fn start(&mut self, topology_bytes: &[u8]) {
419 self.channel.opened = true;
421
422 let mut cfg = Message::configuration(&self.gateway);
424 cfg.data_encode(topology_bytes);
425 self.channel.send(&cfg);
426 self.state = ChannelState::Config;
427 }
428
429 pub fn send_queued(&mut self, msg: Message) {
434 if self.channel.setup && self.send_queue.is_empty() {
435 self.channel.send(&msg);
436 } else {
437 self.send_queue.push_back(msg);
438 }
439 }
440
441 fn send_dequeue(&mut self) {
443 if let Some(msg) = self.send_queue.pop_front() {
444 self.channel.send(&msg);
445 }
446 }
447
448 pub fn shell(&mut self, nodes: &str, command: &[u8], worker_id: u64) {
450 let mut ctl = Message::control(worker_id);
451 if let Message::Control {
452 ref mut action,
453 ref mut target,
454 ..
455 } = ctl
456 {
457 *action = "shell".to_string();
458 *target = nodes.to_string();
459 }
460 ctl.data_encode(command);
461 self.send_queued(ctl);
462 }
463
464 pub fn write(&mut self, nodes: &str, buf: &[u8], worker_id: u64) {
466 let mut ctl = Message::control(worker_id);
467 if let Message::Control {
468 ref mut action,
469 ref mut target,
470 ..
471 } = ctl
472 {
473 *action = "write".to_string();
474 *target = nodes.to_string();
475 }
476 let msgid = ctl.msgid();
477 ctl.data_encode(buf);
478 self.write_history
479 .push_back((msgid, nodes.to_string(), buf.len()));
480 self.send_queued(ctl);
481 }
482
483 pub fn set_write_eof(&mut self, nodes: &str, worker_id: u64) {
485 let mut ctl = Message::control(worker_id);
486 if let Message::Control {
487 ref mut action,
488 ref mut target,
489 ..
490 } = ctl
491 {
492 *action = "eof".to_string();
493 *target = nodes.to_string();
494 }
495 self.send_queued(ctl);
496 }
497
498 pub fn recv(&mut self, msg: Message) -> Result<Vec<PropagationResult>, MessageProcessingError> {
502 let mut new_results = Vec::new();
503
504 match msg.ident() {
505 "END" => {
506 self.state = ChannelState::Closed;
507 return Ok(new_results);
508 }
509 "CHA" => {
510 self.channel.opened = true;
511 return Ok(new_results);
512 }
513 _ => {}
514 }
515
516 match self.state {
517 ChannelState::Config => {
518 if msg.ident() == "ACK" {
520 self.channel.setup = true;
521 self.state = ChannelState::Gather;
522 self.send_dequeue();
523 }
524 }
525 ChannelState::Control | ChannelState::Gather => {
526 match msg.ident() {
527 "ACK" => {
528 if let Message::Ack { ack, .. } = &msg {
530 if let Some(back) = self.write_history.back() {
531 if back.0 == *ack {
532 let _ = self.write_history.pop_back();
533 }
534 }
535 }
536 self.send_dequeue();
537 }
538 "OUT" => {
539 if let Some(nodes_str) = msg.nodes() {
540 if let Ok(decoded) = msg.data_decode() {
541 let ns = NodeSet::parse(nodes_str).unwrap_or_default();
542 for node in ns.iter() {
543 let result = PropagationResult::StdOut {
544 node,
545 data: decoded.clone(),
546 };
547 new_results.push(result);
548 }
549 }
550 }
551 }
552 "SER" => {
553 if let Some(nodes_str) = msg.nodes() {
554 if let Ok(decoded) = msg.data_decode() {
555 let ns = NodeSet::parse(nodes_str).unwrap_or_default();
556 for node in ns.iter() {
557 let result = PropagationResult::StdErr {
558 node,
559 data: decoded.clone(),
560 };
561 new_results.push(result);
562 }
563 }
564 }
565 }
566 "RET" => {
567 if let Message::Retcode { retcode, nodes, .. } = &msg {
568 let ns = NodeSet::parse(nodes).unwrap_or_default();
569 for node in ns.iter() {
570 new_results.push(PropagationResult::Retcode { node, rc: *retcode });
571 }
572 }
573 }
574 "TIM" => {
575 if let Some(nodes_str) = msg.nodes() {
576 let ns = NodeSet::parse(nodes_str).unwrap_or_default();
577 for node in ns.iter() {
578 new_results.push(PropagationResult::Timeout { node });
579 }
580 }
581 }
582 "RTR" => {
583 if let Message::Routing {
584 event,
585 gateway,
586 targets,
587 ..
588 } = &msg
589 {
590 new_results.push(PropagationResult::Routing {
591 event: event.clone(),
592 gateway: gateway.clone(),
593 targets: targets.clone(),
594 });
595 }
596 }
597 "ERR" => {
598 if let Message::Error { reason, .. } = &msg {
599 return Err(MessageProcessingError::ParseError(format!(
600 "{}: {}",
601 self.gateway, reason
602 )));
603 }
604 }
605 _ => {
606 }
608 }
609 }
610 _ => {}
611 }
612
613 self.results.extend(new_results.clone());
614 Ok(new_results)
615 }
616
617 pub fn ev_hup(&mut self, rc: i32) {
619 self.last_rc = Some(rc);
620 }
621
622 pub fn ev_close(&mut self) {
624 self.state = ChannelState::Closed;
625 }
626
627 pub fn current_state(&self) -> ChannelState {
629 self.state
630 }
631
632 pub fn is_gathering(&self) -> bool {
634 self.state == ChannelState::Gather
635 }
636
637 pub fn is_closed(&self) -> bool {
639 self.state == ChannelState::Closed
640 }
641}
642
643#[cfg(test)]
648mod tests {
649 use super::*;
650 use crate::topology::{TopologyGraph, TopologyTree};
651
652 fn build_test_topology() -> TopologyTree {
655 let mut graph = TopologyGraph::new();
656 let admin = NodeSet::parse("admin").unwrap();
657 let gw1 = NodeSet::parse("gw1").unwrap();
658 let gw2 = NodeSet::parse("gw2").unwrap();
659 let nodes1 = NodeSet::parse("node[1-4]").unwrap();
660 let nodes2 = NodeSet::parse("node[5-8]").unwrap();
661
662 graph.add_route(admin.clone(), gw1.clone()).unwrap();
663 graph.add_route(admin, gw2.clone()).unwrap();
664 graph.add_route(gw1, nodes1).unwrap();
665 graph.add_route(gw2, nodes2).unwrap();
666 graph.to_tree("admin").unwrap()
667 }
668
669 #[test]
672 fn test_error_display() {
673 let err = RouteResolvingError::InvalidRoot("badnode".into());
674 assert!(err.to_string().contains("badnode"));
675
676 let err = RouteResolvingError::NoRoute("node99".into());
677 assert!(err.to_string().contains("node99"));
678
679 let err = RouteResolvingError::Unreachable("gw1".into());
680 assert!(err.to_string().contains("gw1"));
681
682 let err = RouteResolvingError::SameSourceDest("a".into(), "a".into());
683 assert!(err.to_string().contains("a"));
684 }
685
686 #[test]
689 fn test_router_creation() {
690 let tree = build_test_topology();
691 let router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
692 assert_eq!(router.root(), "admin");
693 assert_eq!(router.fanout(), 0);
694 }
695
696 #[test]
697 fn test_router_invalid_root() {
698 let tree = build_test_topology();
699 let result = PropagationTreeRouter::new("nonexistent", &tree, 0);
700 assert!(result.is_err());
701 }
702
703 #[test]
704 fn test_router_next_hop() {
705 let tree = build_test_topology();
706 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
707
708 let dst = NodeSet::parse("node1").unwrap();
710 let hop = router.next_hop(&dst).unwrap();
711 assert_eq!(hop.to_string(), "gw1");
712
713 let dst = NodeSet::parse("node5").unwrap();
715 let hop = router.next_hop(&dst).unwrap();
716 assert_eq!(hop.to_string(), "gw2");
717 }
718
719 #[test]
720 fn test_router_same_source_dest() {
721 let tree = build_test_topology();
722 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
723 let dst = NodeSet::parse("admin").unwrap();
724 let result = router.next_hop(&dst);
725 assert!(result.is_err());
726 }
727
728 #[test]
729 fn test_router_mark_unreachable() {
730 let tree = build_test_topology();
731 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
732
733 router.mark_unreachable("gw1");
734
735 let dst = NodeSet::parse("node1").unwrap();
738 let result = router.next_hop(&dst);
739 assert!(result.is_err());
740 }
741
742 #[test]
743 fn test_router_dispatch_directly_connected() {
744 let tree = build_test_topology();
745 let router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
746
747 let dst = NodeSet::parse("localnode").unwrap();
749 let results = router.dispatch(&dst);
750 assert_eq!(results.len(), 1);
751 assert_eq!(results[0].0.to_string(), "localnode");
752 }
753
754 #[test]
755 fn test_router_dispatch_remote() {
756 let tree = build_test_topology();
757 let router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
758
759 let dst = NodeSet::parse("node[1-2]").unwrap();
760 let results = router.dispatch(&dst);
761 assert!(!results.is_empty());
763 for (hop, _target) in &results {
764 assert_eq!(hop.to_string(), "gw1");
765 }
766 }
767
768 #[test]
769 fn test_router_load_balancing() {
770 let mut graph = TopologyGraph::new();
773 let admin = NodeSet::parse("admin").unwrap();
774 let gws = NodeSet::parse("gw[1-2]").unwrap();
775 let nodes = NodeSet::parse("node[1-8]").unwrap();
776 graph.add_route(admin, gws).unwrap();
777
778 let gw1 = NodeSet::parse("gw1").unwrap();
780 let gw2 = NodeSet::parse("gw2").unwrap();
781 graph
782 .add_route(gw1, NodeSet::parse("node[1-4]").unwrap())
783 .unwrap();
784 graph
785 .add_route(gw2, NodeSet::parse("node[5-8]").unwrap())
786 .unwrap();
787
788 let tree = graph.to_tree("admin").unwrap();
789 let mut router = PropagationTreeRouter::new("admin", &tree, 0).unwrap();
790
791 let d1 = NodeSet::parse("node1").unwrap();
793 let _h1 = router.next_hop(&d1).unwrap();
794 let d2 = NodeSet::parse("node5").unwrap();
795 let _h2 = router.next_hop(&d2).unwrap();
796
797 }
800
801 #[test]
804 fn test_channel_creation() {
805 let ch = PropagationChannel::new("gw1");
806 assert_eq!(ch.gateway, "gw1");
807 assert_eq!(ch.current_state(), ChannelState::Init);
808 assert!(!ch.is_gathering());
809 assert!(!ch.is_closed());
810 }
811
812 #[test]
813 fn test_channel_start() {
814 let mut ch = PropagationChannel::new("gw1");
815 ch.start(b"topology data");
816 assert_eq!(ch.current_state(), ChannelState::Config);
817 assert!(ch.channel.opened);
818
819 let outgoing = ch.channel.take_outgoing().unwrap();
821 let xml = String::from_utf8(outgoing).unwrap();
822 assert!(xml.contains("type=\"CFG\""));
823 assert!(xml.contains("gateway=\"gw1\""));
824 }
825
826 #[test]
827 fn test_channel_config_ack() {
828 let mut ch = PropagationChannel::new("gw1");
829 ch.start(b"topo");
830 assert_eq!(ch.state, ChannelState::Config);
831
832 let ack = Message::ack(0);
834 let results = ch.recv(ack).unwrap();
835 assert!(results.is_empty());
836 assert_eq!(ch.state, ChannelState::Gather);
837 assert!(ch.channel.setup);
838 }
839
840 #[test]
841 fn test_channel_shell_command() {
842 let mut ch = PropagationChannel::new("gw1");
843 ch.start(b"topo");
844
845 ch.recv(Message::ack(0)).unwrap();
847
848 ch.shell("node[1-4]", b"uname -a", 42);
850
851 let outgoing = ch.channel.take_outgoing().unwrap();
853 let outgoing2 = ch.channel.take_outgoing().unwrap();
855 let xml = String::from_utf8(outgoing2).unwrap();
856 assert!(xml.contains("type=\"CTL\""));
857 assert!(xml.contains("action=\"shell\""));
858 assert!(xml.contains("target=\"node[1-4]\""));
859 }
860
861 #[test]
862 fn test_channel_recv_stdout() {
863 let mut ch = PropagationChannel::new("gw1");
864 ch.start(b"topo");
865 ch.recv(Message::ack(0)).unwrap();
866
867 let mut stdout = Message::stdout("node[1-2]", 1);
869 stdout.data_encode(b"hello world");
870 let results = ch.recv(stdout).unwrap();
871
872 assert_eq!(results.len(), 2);
873 for r in &results {
874 if let PropagationResult::StdOut { data, .. } = r {
875 assert_eq!(data, b"hello world");
876 } else {
877 panic!("expected StdOut");
878 }
879 }
880 }
881
882 #[test]
883 fn test_channel_recv_stderr() {
884 let mut ch = PropagationChannel::new("gw1");
885 ch.start(b"topo");
886 ch.recv(Message::ack(0)).unwrap();
887
888 let mut stderr = Message::stderr("node1", 1);
889 stderr.data_encode(b"error output");
890 let results = ch.recv(stderr).unwrap();
891
892 assert_eq!(results.len(), 1);
893 if let PropagationResult::StdErr { node, data } = &results[0] {
894 assert_eq!(node, "node1");
895 assert_eq!(data, b"error output");
896 } else {
897 panic!("expected StdErr");
898 }
899 }
900
901 #[test]
902 fn test_channel_recv_retcode() {
903 let mut ch = PropagationChannel::new("gw1");
904 ch.start(b"topo");
905 ch.recv(Message::ack(0)).unwrap();
906
907 let ret = Message::retcode("node[1-3]", 0, 1);
908 let results = ch.recv(ret).unwrap();
909
910 assert_eq!(results.len(), 3);
911 for r in &results {
912 if let PropagationResult::Retcode { rc, .. } = r {
913 assert_eq!(*rc, 0);
914 } else {
915 panic!("expected Retcode");
916 }
917 }
918 }
919
920 #[test]
921 fn test_channel_recv_timeout() {
922 let mut ch = PropagationChannel::new("gw1");
923 ch.start(b"topo");
924 ch.recv(Message::ack(0)).unwrap();
925
926 let tim = Message::timeout("node[5-6]", 1);
927 let results = ch.recv(tim).unwrap();
928
929 assert_eq!(results.len(), 2);
930 for r in &results {
931 assert!(matches!(r, PropagationResult::Timeout { .. }));
932 }
933 }
934
935 #[test]
936 fn test_channel_recv_routing() {
937 let mut ch = PropagationChannel::new("gw1");
938 ch.start(b"topo");
939 ch.recv(Message::ack(0)).unwrap();
940
941 let rtr = Message::routing("connect", "gw2", "node[10-20]", 1);
942 let results = ch.recv(rtr).unwrap();
943
944 assert_eq!(results.len(), 1);
945 if let PropagationResult::Routing {
946 event,
947 gateway,
948 targets,
949 } = &results[0]
950 {
951 assert_eq!(event, "connect");
952 assert_eq!(gateway, "gw2");
953 assert_eq!(targets, "node[10-20]");
954 } else {
955 panic!("expected Routing");
956 }
957 }
958
959 #[test]
960 fn test_channel_recv_error() {
961 let mut ch = PropagationChannel::new("gw1");
962 ch.start(b"topo");
963 ch.recv(Message::ack(0)).unwrap();
964
965 let err = Message::error("something went wrong");
966 let result = ch.recv(err);
967 assert!(result.is_err());
968 }
969
970 #[test]
971 fn test_channel_recv_end() {
972 let mut ch = PropagationChannel::new("gw1");
973 ch.start(b"topo");
974 ch.recv(Message::ack(0)).unwrap();
975
976 let end = Message::end();
977 ch.recv(end).unwrap();
978 assert!(ch.is_closed());
979 }
980
981 #[test]
982 fn test_channel_ev_hup() {
983 let mut ch = PropagationChannel::new("gw1");
984 ch.ev_hup(1);
985 assert_eq!(ch.last_rc, Some(1));
986 }
987
988 #[test]
989 fn test_channel_ev_close() {
990 let mut ch = PropagationChannel::new("gw1");
991 ch.ev_close();
992 assert!(ch.is_closed());
993 }
994
995 #[test]
996 fn test_channel_send_queue() {
997 let mut ch = PropagationChannel::new("gw1");
998 ch.start(b"topo");
999 ch.shell("node1", b"echo hi", 1);
1001
1002 let _cfg = ch.channel.take_outgoing().unwrap();
1004 assert!(ch.channel.take_outgoing().is_none());
1005
1006 ch.recv(Message::ack(0)).unwrap();
1008 let ctl = ch.channel.take_outgoing().unwrap();
1009 let xml = String::from_utf8(ctl).unwrap();
1010 assert!(xml.contains("type=\"CTL\""));
1011 }
1012
1013 #[test]
1014 fn test_channel_write_and_eof() {
1015 let mut ch = PropagationChannel::new("gw1");
1016 ch.start(b"topo");
1017 ch.recv(Message::ack(0)).unwrap();
1018
1019 ch.write("node1", b"input data", 1);
1020 ch.set_write_eof("node1", 1);
1021
1022 let _ = ch.channel.take_outgoing(); let write_msg = ch.channel.take_outgoing().unwrap();
1024 let xml = String::from_utf8(write_msg).unwrap();
1025 assert!(xml.contains("action=\"write\""));
1026
1027 let eof_msg = ch.channel.take_outgoing().unwrap();
1028 let xml2 = String::from_utf8(eof_msg).unwrap();
1029 assert!(xml2.contains("action=\"eof\""));
1030 }
1031
1032 #[test]
1033 fn test_channel_results_accumulate() {
1034 let mut ch = PropagationChannel::new("gw1");
1035 ch.start(b"topo");
1036 ch.recv(Message::ack(0)).unwrap();
1037
1038 let mut out1 = Message::stdout("node1", 1);
1040 out1.data_encode(b"line1");
1041 ch.recv(out1).unwrap();
1042
1043 let mut out2 = Message::stdout("node2", 1);
1044 out2.data_encode(b"line2");
1045 ch.recv(out2).unwrap();
1046
1047 let ret = Message::retcode("node[1-2]", 0, 1);
1048 ch.recv(ret).unwrap();
1049
1050 assert_eq!(ch.results.len(), 4);
1052 }
1053}