| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- use super::*;
- fn cfg_with_ha() -> Config {
- let toml = r#"
- ssh_user = "u"
- ssh_key_path = "/k"
- [[clusters]]
- name = "x"
- master_ib_ip = "1.1.1.1"
- nodes = [
- { name = "n1", ssh_ip = "10.0.0.1", ib_ip = "10.0.0.1", rank = 0, entity = "switch.n1" },
- { name = "n2", ssh_ip = "10.0.0.2", ib_ip = "10.0.0.2", rank = 1 },
- ]
- extra_entities = ["switch.network_x"]
- [nccl."x"]
- ib_hca = "h"
- socket_ifname = "s"
- ib_gid_index = 1
- ib_disable = 0
- [home_assistant]
- url = "http://ha.local:8123"
- token = "abcdef0123456789abcdef0123456789"
- shutdown_delay_seconds = 5
- "#;
- toml::from_str(toml).unwrap()
- }
- fn cfg_no_ha() -> Config {
- let toml = r#"
- ssh_user = "u"
- ssh_key_path = "/k"
- [[clusters]]
- name = "x"
- master_ib_ip = "1.1.1.1"
- nodes = [
- { name = "n1", ssh_ip = "10.0.0.1", ib_ip = "10.0.0.1", rank = 0, entity = "switch.n1" },
- ]
- [nccl."x"]
- ib_hca = "h"
- socket_ifname = "s"
- ib_gid_index = 1
- ib_disable = 0
- "#;
- toml::from_str(toml).unwrap()
- }
- #[test]
- fn select_targets_filters_by_node() {
- let cfg = cfg_with_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- let t = select_targets(&c, &["n1".to_string()], "test").unwrap();
- assert_eq!(t.len(), 1);
- assert_eq!(t[0].name, "n1");
- }
- #[test]
- fn select_targets_all() {
- let cfg = cfg_with_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- let t = select_targets(&c, &[], "test").unwrap();
- assert_eq!(t.len(), 2);
- }
- #[test]
- fn select_targets_multiple_nodes() {
- // `stop --node a --node b --off` must power off *both* nodes, not just
- // the first. (Regression for I-5.)
- let cfg = cfg_with_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- let t = select_targets(&c, &["n1".to_string(), "n2".to_string()], "test").unwrap();
- assert_eq!(t.len(), 2);
- let names: Vec<_> = t.iter().map(|n| n.name.as_str()).collect();
- assert!(names.contains(&"n1"));
- assert!(names.contains(&"n2"));
- }
- #[test]
- fn select_targets_partial_missing_errors() {
- let cfg = cfg_with_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- // One valid + one missing → error listing the missing one.
- let r = select_targets(&c, &["n1".to_string(), "nope".to_string()], "test");
- assert!(r.is_err());
- assert!(r.unwrap_err().to_string().contains("nope"));
- }
- #[test]
- fn select_targets_unknown_node_errors() {
- let cfg = cfg_with_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- let r = select_targets(&c, &["nope".to_string()], "test");
- assert!(r.is_err());
- }
- #[test]
- fn power_on_requires_ha_config() {
- let cfg = cfg_no_ha();
- assert!(cfg.home_assistant.is_none());
- }
- #[test]
- fn cluster_extra_entities_parsed() {
- use crate::config::ExtraEntity;
- let cfg = cfg_with_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- assert_eq!(
- c.extra_entities,
- vec![ExtraEntity::Simple("switch.network_x".into())]
- );
- }
- #[test]
- fn extra_entity_forms_blocking_and_soft() {
- use crate::config::ExtraEntity;
- let toml = r#"
- ssh_user = "u"
- ssh_key_path = "/k"
- [[clusters]]
- name = "x"
- nodes = [{ name = "n1", ssh_ip = "10.0.0.1", rank = 0 }]
- extra_entities = ["switch.plug", { entity = "switch.fan", blocks = false }, { entity = "switch.other" }]
- "#;
- let cfg: crate::config::Config = toml::from_str(toml).unwrap();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- let e = &c.extra_entities;
- assert_eq!(e.len(), 3);
- assert!(matches!(&e[0], ExtraEntity::Simple(s) if s == "switch.plug"));
- assert!(e[0].blocks());
- assert!(
- matches!(&e[1], ExtraEntity::Detailed { entity, blocks } if entity == "switch.fan" && !blocks)
- );
- assert!(!e[1].blocks());
- // table form without `blocks` defaults to blocking
- assert!(
- matches!(&e[2], ExtraEntity::Detailed { entity, blocks } if entity == "switch.other" && *blocks)
- );
- assert!(e[2].blocks());
- assert_eq!(e[1].entity(), "switch.fan");
- }
- #[test]
- fn cluster_without_extra_entities_is_empty() {
- let cfg = cfg_no_ha();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- assert!(c.extra_entities.is_empty());
- }
- #[test]
- fn mikrotik_dispatch_lookup() {
- let cfg = cfg_with_ha();
- // No [mikrotik] in this config — every entity stays on the HA path.
- assert!(super::mikro::device(cfg.mikrotik.as_ref(), "switch.n1").is_none());
- let toml = r#"
- ssh_user = "u"
- ssh_key_path = "/k"
- [[clusters]]
- name = "x"
- nodes = [{ name = "n1", ssh_ip = "10.0.0.1", rank = 0, entity = "router.lab" }]
- extra_entities = ["router.lab", "switch.plug"]
- [mikrotik]
- [[mikrotik.devices]]
- name = "router.lab"
- address = "192.168.88.1"
- username = "admin"
- password = "p"
- "#;
- let cfg: crate::config::Config = toml::from_str(toml).unwrap();
- let c = crate::config::select_cluster_by_name(&cfg, "x").unwrap();
- let off = super::mikro::collect_off(&cfg, &c.nodes, &super::extra_ids(&c));
- assert_eq!(off, vec!["router.lab".to_string()]);
- // same device referenced twice (node + extra) → listed once
- let dev = super::mikro::device(cfg.mikrotik.as_ref(), "router.lab");
- assert!(dev.is_some());
- }
|