tests.rs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. use super::*;
  2. fn cfg_with_ha() -> Config {
  3. let toml = r#"
  4. ssh_user = "u"
  5. ssh_key_path = "/k"
  6. [[clusters]]
  7. name = "x"
  8. master_ib_ip = "1.1.1.1"
  9. nodes = [
  10. { name = "n1", ssh_ip = "10.0.0.1", ib_ip = "10.0.0.1", rank = 0, entity = "switch.n1" },
  11. { name = "n2", ssh_ip = "10.0.0.2", ib_ip = "10.0.0.2", rank = 1 },
  12. ]
  13. extra_entities = ["switch.network_x"]
  14. [nccl."x"]
  15. ib_hca = "h"
  16. socket_ifname = "s"
  17. ib_gid_index = 1
  18. ib_disable = 0
  19. [home_assistant]
  20. url = "http://ha.local:8123"
  21. token = "abcdef0123456789abcdef0123456789"
  22. shutdown_delay_seconds = 5
  23. "#;
  24. toml::from_str(toml).unwrap()
  25. }
  26. fn cfg_no_ha() -> Config {
  27. let toml = r#"
  28. ssh_user = "u"
  29. ssh_key_path = "/k"
  30. [[clusters]]
  31. name = "x"
  32. master_ib_ip = "1.1.1.1"
  33. nodes = [
  34. { name = "n1", ssh_ip = "10.0.0.1", ib_ip = "10.0.0.1", rank = 0, entity = "switch.n1" },
  35. ]
  36. [nccl."x"]
  37. ib_hca = "h"
  38. socket_ifname = "s"
  39. ib_gid_index = 1
  40. ib_disable = 0
  41. "#;
  42. toml::from_str(toml).unwrap()
  43. }
  44. #[test]
  45. fn select_targets_filters_by_node() {
  46. let cfg = cfg_with_ha();
  47. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  48. let t = select_targets(&c, &["n1".to_string()], "test").unwrap();
  49. assert_eq!(t.len(), 1);
  50. assert_eq!(t[0].name, "n1");
  51. }
  52. #[test]
  53. fn select_targets_all() {
  54. let cfg = cfg_with_ha();
  55. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  56. let t = select_targets(&c, &[], "test").unwrap();
  57. assert_eq!(t.len(), 2);
  58. }
  59. #[test]
  60. fn select_targets_multiple_nodes() {
  61. // `stop --node a --node b --off` must power off *both* nodes, not just
  62. // the first. (Regression for I-5.)
  63. let cfg = cfg_with_ha();
  64. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  65. let t = select_targets(&c, &["n1".to_string(), "n2".to_string()], "test").unwrap();
  66. assert_eq!(t.len(), 2);
  67. let names: Vec<_> = t.iter().map(|n| n.name.as_str()).collect();
  68. assert!(names.contains(&"n1"));
  69. assert!(names.contains(&"n2"));
  70. }
  71. #[test]
  72. fn select_targets_partial_missing_errors() {
  73. let cfg = cfg_with_ha();
  74. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  75. // One valid + one missing → error listing the missing one.
  76. let r = select_targets(&c, &["n1".to_string(), "nope".to_string()], "test");
  77. assert!(r.is_err());
  78. assert!(r.unwrap_err().to_string().contains("nope"));
  79. }
  80. #[test]
  81. fn select_targets_unknown_node_errors() {
  82. let cfg = cfg_with_ha();
  83. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  84. let r = select_targets(&c, &["nope".to_string()], "test");
  85. assert!(r.is_err());
  86. }
  87. #[test]
  88. fn power_on_requires_ha_config() {
  89. let cfg = cfg_no_ha();
  90. assert!(cfg.home_assistant.is_none());
  91. }
  92. #[test]
  93. fn cluster_extra_entities_parsed() {
  94. use crate::config::ExtraEntity;
  95. let cfg = cfg_with_ha();
  96. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  97. assert_eq!(
  98. c.extra_entities,
  99. vec![ExtraEntity::Simple("switch.network_x".into())]
  100. );
  101. }
  102. #[test]
  103. fn extra_entity_forms_blocking_and_soft() {
  104. use crate::config::ExtraEntity;
  105. let toml = r#"
  106. ssh_user = "u"
  107. ssh_key_path = "/k"
  108. [[clusters]]
  109. name = "x"
  110. nodes = [{ name = "n1", ssh_ip = "10.0.0.1", rank = 0 }]
  111. extra_entities = ["switch.plug", { entity = "switch.fan", blocks = false }, { entity = "switch.other" }]
  112. "#;
  113. let cfg: crate::config::Config = toml::from_str(toml).unwrap();
  114. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  115. let e = &c.extra_entities;
  116. assert_eq!(e.len(), 3);
  117. assert!(matches!(&e[0], ExtraEntity::Simple(s) if s == "switch.plug"));
  118. assert!(e[0].blocks());
  119. assert!(
  120. matches!(&e[1], ExtraEntity::Detailed { entity, blocks } if entity == "switch.fan" && !blocks)
  121. );
  122. assert!(!e[1].blocks());
  123. // table form without `blocks` defaults to blocking
  124. assert!(
  125. matches!(&e[2], ExtraEntity::Detailed { entity, blocks } if entity == "switch.other" && *blocks)
  126. );
  127. assert!(e[2].blocks());
  128. assert_eq!(e[1].entity(), "switch.fan");
  129. }
  130. #[test]
  131. fn cluster_without_extra_entities_is_empty() {
  132. let cfg = cfg_no_ha();
  133. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  134. assert!(c.extra_entities.is_empty());
  135. }
  136. #[test]
  137. fn mikrotik_dispatch_lookup() {
  138. let cfg = cfg_with_ha();
  139. // No [mikrotik] in this config — every entity stays on the HA path.
  140. assert!(super::mikro::device(cfg.mikrotik.as_ref(), "switch.n1").is_none());
  141. let toml = r#"
  142. ssh_user = "u"
  143. ssh_key_path = "/k"
  144. [[clusters]]
  145. name = "x"
  146. nodes = [{ name = "n1", ssh_ip = "10.0.0.1", rank = 0, entity = "router.lab" }]
  147. extra_entities = ["router.lab", "switch.plug"]
  148. [mikrotik]
  149. [[mikrotik.devices]]
  150. name = "router.lab"
  151. address = "192.168.88.1"
  152. username = "admin"
  153. password = "p"
  154. "#;
  155. let cfg: crate::config::Config = toml::from_str(toml).unwrap();
  156. let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
  157. let off = super::mikro::collect_off(&cfg, &c.nodes, &super::extra_ids(&c));
  158. assert_eq!(off, vec!["router.lab".to_string()]);
  159. // same device referenced twice (node + extra) → listed once
  160. let dev = super::mikro::device(cfg.mikrotik.as_ref(), "router.lab");
  161. assert!(dev.is_some());
  162. }