ProxmoxMacBridgeTests.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. using RackPeek.Domain.Api;
  2. using RackPeek.Domain.Discovery;
  3. using RackPeek.Domain.Resources;
  4. using RackPeek.Domain.Resources.SystemResources;
  5. namespace Tests.Discovery;
  6. /// <summary>
  7. /// Proxmox's side of the MAC bridge: guest configs carry the NIC MACs Proxmox
  8. /// assigned, a network scan sees exactly those MACs on the wire, and the resolver
  9. /// lands both collectors on one card — the same contract the system collector has.
  10. /// </summary>
  11. public class ProxmoxMacBridgeTests {
  12. // -- parsing ------------------------------------------------------------------------
  13. [Theory]
  14. [InlineData("pve-qemu-config.json", "bc:24:11:12:34:56")] // virtio=BC:24:11:…
  15. [InlineData("pve-lxc-config.json", "bc:24:11:aa:bb:cc")] // hwaddr=BC:24:11:…
  16. [InlineData("pve-lxc-config-dhcp.json", "bc:24:11:dd:ee:ff")] // dhcp still has a MAC
  17. public void A_guests_config_yields_its_normalised_mac(string fixture, string expected) {
  18. ProxmoxGuestConfig config = ProxmoxResponseParser.ParseGuestConfig(Fixture.Read(fixture));
  19. Assert.Equal([expected], config.Macs);
  20. }
  21. [Theory]
  22. [InlineData("""{"data":{}}""")]
  23. [InlineData("""{"data":{"ostype":"l26","scsi0":"local-lvm:vm-1-disk-0,size=64G"}}""")]
  24. [InlineData("""{"data":{"net0":"bridge=vmbr0,firewall=1"}}""")] // a net line with no MAC
  25. [InlineData("""{"data":{"network":"virtio=BC:24:11:12:34:56"}}""")] // not a netN slot
  26. public void A_config_without_nic_macs_yields_none(string json) {
  27. ProxmoxGuestConfig config = ProxmoxResponseParser.ParseGuestConfig(json);
  28. Assert.Empty(config.Macs ?? []);
  29. }
  30. [Fact]
  31. public void Every_nic_of_a_multi_homed_guest_is_recorded() {
  32. ProxmoxGuestConfig config = ProxmoxResponseParser.ParseGuestConfig(
  33. """
  34. {"data":{
  35. "net0":"virtio=BC:24:11:12:34:56,bridge=vmbr0",
  36. "net1":"e1000=BC:24:11:99:88:77,bridge=vmbr1,tag=50"
  37. }}
  38. """);
  39. Assert.Equal(["bc:24:11:12:34:56", "bc:24:11:99:88:77"], config.Macs);
  40. }
  41. // -- mapping ------------------------------------------------------------------------
  42. [Fact]
  43. public void A_guest_card_carries_its_macs_label() {
  44. List<Resource> resources = ProxmoxResourceMapper.ToResources(
  45. "homelab",
  46. [new ProxmoxNode { Name = "pve01" }],
  47. [
  48. new ProxmoxGuest {
  49. VmId = 104,
  50. Node = "pve01",
  51. Name = "docker-01",
  52. Type = "vm",
  53. Macs = ["bc:24:11:12:34:56"]
  54. }
  55. ]);
  56. SystemResource guest = resources.OfType<SystemResource>().Single(r => r.Name == "docker-01");
  57. Assert.Equal("bc:24:11:12:34:56", guest.Labels["macs"]);
  58. }
  59. // -- the bridge, end to end through the real server ----------------------------------
  60. [Fact]
  61. public async Task A_scanned_guest_and_its_proxmox_card_become_one() {
  62. // The sweep found the VM on the LAN first — by the very MAC Proxmox assigned it.
  63. var scanned = DiscoveryDocument.ToYaml(NetworkScanMapper.ToResources([
  64. new NetworkHostFact("192.168.1.178", "bc:24:11:12:34:56", null, true, [])
  65. ]));
  66. using var api = new DiscoveryApiFixture(scanned);
  67. // Now `rpk discover proxmox` reports the estate, including that guest.
  68. List<Resource> estate = ProxmoxResourceMapper.ToResources(
  69. "homelab",
  70. [new ProxmoxNode { Name = "pve01" }],
  71. [
  72. new ProxmoxGuest {
  73. VmId = 104,
  74. Node = "pve01",
  75. Name = "docker-01",
  76. Type = "vm",
  77. Cores = 4,
  78. Os = "Linux",
  79. Macs = ["bc:24:11:12:34:56"]
  80. }
  81. ]);
  82. ImportYamlResponse response = await api.PublishAsync(DiscoveryDocument.ToYaml(estate));
  83. var stored = api.StoredYaml;
  84. // The guest landed on the scan's card: identity upgraded to the vmid-based id,
  85. // the scan's address kept, no duplicate for the same machine.
  86. Assert.DoesNotContain("rpk1:net:", stored);
  87. Assert.Contains("rpk1:pve:", stored);
  88. Assert.Contains("ip: 192.168.1.178", stored);
  89. Assert.Contains("os: Linux", stored);
  90. Assert.DoesNotContain(response.Added, name => name.StartsWith("docker-01"));
  91. Fixture.AssertConformsToSchema(stored);
  92. // And a rescan afterwards still lands on that same card via the MAC.
  93. ImportYamlResponse rescan = await api.PublishAsync(DiscoveryDocument.ToYaml(
  94. NetworkScanMapper.ToResources([
  95. new NetworkHostFact("192.168.1.179", "bc:24:11:12:34:56", null, true, [])
  96. ])));
  97. Assert.Empty(rescan.Added);
  98. Assert.Contains("rpk1:pve:", api.StoredYaml);
  99. Assert.Contains("ip: 192.168.1.179", api.StoredYaml);
  100. }
  101. }