MacUnificationTests.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. using RackPeek.Domain.Discovery;
  2. using RackPeek.Domain.Resources;
  3. using RackPeek.Domain.Resources.Servers;
  4. using RackPeek.Domain.Resources.SystemResources;
  5. namespace Tests.Discovery;
  6. /// <summary>
  7. /// The MAC bridge between collectors: an agent card carries the machine's MACs
  8. /// (a "macs" label), a scan card carries the one it was found by (a "mac" label),
  9. /// and a shared MAC means the same box — so both collectors land on one card,
  10. /// whichever arrived first. These pin the resolver's side of that contract.
  11. /// </summary>
  12. public class MacUnificationTests {
  13. private const string _mac = "dc:a6:32:0f:11:22";
  14. private static SystemResource ScanCard(string name = "host-595109fb", string mac = _mac) => new() {
  15. Kind = SystemResource.KindLabel,
  16. Name = name,
  17. DiscoveryId = DiscoveryId.Create(DiscoveryId.NetworkScheme, mac),
  18. Ip = "192.168.1.20",
  19. Labels = { ["mac"] = mac }
  20. };
  21. private static SystemResource AgentCard(
  22. string name = "nas01",
  23. string machineId = "machine-a",
  24. string macs = _mac) => new() {
  25. Kind = SystemResource.KindLabel,
  26. Name = name,
  27. DiscoveryId = DiscoveryId.Create(DiscoveryId.SystemScheme, machineId),
  28. Type = "baremetal",
  29. Os = "Debian",
  30. Cores = 12,
  31. Labels = { ["macs"] = macs }
  32. };
  33. [Fact]
  34. public void An_agent_claims_the_scan_card_and_upgrades_its_identity() {
  35. // Scan ran first; now `rpk discover system` reports the same box.
  36. List<Resource> existing = [ScanCard()];
  37. List<Resource> incoming = [AgentCard()];
  38. DiscoveryIdResolver.ResolveNames(existing, incoming);
  39. // The stored card's name wins (names are user-owned), and the agent's id
  40. // survives so the merge upgrades the card to the stronger identity.
  41. Assert.Equal("host-595109fb", incoming[0].Name);
  42. Assert.StartsWith("rpk1:sys:", incoming[0].DiscoveryId);
  43. }
  44. [Fact]
  45. public void A_scan_enriches_the_agents_card_without_touching_its_identity() {
  46. // Agent ran first; now a sweep sees the same box from outside.
  47. List<Resource> existing = [AgentCard()];
  48. List<Resource> incoming = [ScanCard()];
  49. DiscoveryIdResolver.ResolveNames(existing, incoming);
  50. Assert.Equal("nas01", incoming[0].Name);
  51. // The weak scan id is dropped so the merge cannot downgrade the sys id.
  52. Assert.Null(incoming[0].DiscoveryId);
  53. }
  54. [Fact]
  55. public void The_macs_are_matched_however_each_side_spells_them() {
  56. // The agent records padded lowercase; suppose a stored label was hand-edited
  57. // to Windows-style dashes — normalisation makes them the same machine anyway.
  58. SystemResource stored = ScanCard();
  59. stored.Labels["mac"] = "DC-A6-32-0F-11-22";
  60. List<Resource> existing = [stored];
  61. List<Resource> incoming = [AgentCard()];
  62. DiscoveryIdResolver.ResolveNames(existing, incoming);
  63. Assert.Equal("host-595109fb", incoming[0].Name);
  64. }
  65. [Fact]
  66. public void An_id_match_always_beats_a_mac_match() {
  67. // The scan card was renamed by the user; a rescan must follow its own id to
  68. // the rename, not rediscover it via the MAC of some other card.
  69. SystemResource renamed = ScanCard(name: "storage-primary");
  70. List<Resource> existing = [renamed, AgentCard(macs: _mac)];
  71. List<Resource> incoming = [ScanCard()];
  72. DiscoveryIdResolver.ResolveNames(existing, incoming);
  73. Assert.Equal("storage-primary", incoming[0].Name);
  74. Assert.NotNull(incoming[0].DiscoveryId);
  75. }
  76. [Fact]
  77. public void A_mac_claimed_by_two_stored_cards_identifies_nothing() {
  78. // Ambiguity never unifies: fall through to the ordinary name rules.
  79. List<Resource> existing = [
  80. AgentCard(name: "clone-a", machineId: "machine-a"),
  81. AgentCard(name: "clone-b", machineId: "machine-b")
  82. ];
  83. List<Resource> incoming = [ScanCard(name: "host-xyz")];
  84. DiscoveryIdResolver.ResolveNames(existing, incoming);
  85. Assert.Equal("host-xyz", incoming[0].Name);
  86. Assert.NotNull(incoming[0].DiscoveryId);
  87. }
  88. [Fact]
  89. public void Two_agent_grade_identities_sharing_a_mac_never_unify() {
  90. // Cloned VMs can share a NIC MAC while having distinct machine-ids; the
  91. // machine-id is the authority between agent-grade collectors.
  92. List<Resource> existing = [AgentCard(name: "vm-a", machineId: "machine-a")];
  93. List<Resource> incoming = [AgentCard(name: "vm-b", machineId: "machine-b")];
  94. DiscoveryIdResolver.ResolveNames(existing, incoming);
  95. Assert.Equal("vm-b", incoming[0].Name);
  96. }
  97. [Fact]
  98. public void A_mac_on_a_different_kind_of_card_is_not_a_bridge() {
  99. // The user put a mac label on the Server card describing the box's hardware;
  100. // the scan's System card is a different kind of thing and stays separate.
  101. var server = new Server {
  102. Kind = "Server",
  103. Name = "rack-server",
  104. Labels = { ["mac"] = _mac }
  105. };
  106. List<Resource> incoming = [ScanCard(name: "host-xyz")];
  107. DiscoveryIdResolver.ResolveNames([server], incoming);
  108. Assert.Equal("host-xyz", incoming[0].Name);
  109. }
  110. [Fact]
  111. public void A_hand_written_card_with_a_mac_label_is_adopted_like_a_name_match() {
  112. // No id on the stored card: whoever arrives first with an identity stamps it.
  113. var handWritten = new SystemResource {
  114. Kind = SystemResource.KindLabel,
  115. Name = "nas01",
  116. Type = "baremetal",
  117. Labels = { ["mac"] = _mac }
  118. };
  119. List<Resource> incoming = [ScanCard(name: "host-xyz")];
  120. DiscoveryIdResolver.ResolveNames([handWritten], incoming);
  121. Assert.Equal("nas01", incoming[0].Name);
  122. Assert.NotNull(incoming[0].DiscoveryId); // the scan id gets stamped on
  123. }
  124. }