diff --git a/go.mod b/go.mod index f31c04b8..2fb92ae3 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/prometheus/common v0.70.1 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.12.1 - github.com/unpoller/unifi/v6 v6.0.2 + github.com/unpoller/unifi/v6 v6.0.3 go.opentelemetry.io/otel v1.46.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.46.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.46.0 diff --git a/go.sum b/go.sum index 17e75164..8291259e 100644 --- a/go.sum +++ b/go.sum @@ -121,8 +121,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= -github.com/unpoller/unifi/v6 v6.0.2 h1:Mzcn0zSTnFxMZuFIoVqhSkECT1sX9S7RJ8e/ZxikOcw= -github.com/unpoller/unifi/v6 v6.0.2/go.mod h1:d7dz1cBxVbbFZobNRcdUHHYtTdicVyZ05J5OmgoINa8= +github.com/unpoller/unifi/v6 v6.0.3 h1:NoxbSA5HLMErs/1yVkmuwm+lcyy9SxSfOLDW58PKqrQ= +github.com/unpoller/unifi/v6 v6.0.3/go.mod h1:d7dz1cBxVbbFZobNRcdUHHYtTdicVyZ05J5OmgoINa8= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= diff --git a/pkg/promunifi/wan.go b/pkg/promunifi/wan.go index e40a12b2..4ffd15db 100644 --- a/pkg/promunifi/wan.go +++ b/pkg/promunifi/wan.go @@ -87,8 +87,8 @@ func (u *promUnifi) exportWAN(r report, w *unifi.WANEnrichedConfiguration) { cfg.WANNetworkgroup, cfg.WANType, cfg.WANLoadBalanceType, - "", // site_name - will be set by caller if available - "", // source - will be set by caller if available + w.SiteName, + w.SourceName, } // Convert boolean FlexBool values to float64 @@ -130,8 +130,8 @@ func (u *promUnifi) exportWAN(r report, w *unifi.WANEnrichedConfiguration) { cfg.WANNetworkgroup, details.ServiceProvider.Name, details.ServiceProvider.City, - "", // site_name - "", // source + w.SiteName, + w.SourceName, } metrics = append(metrics, &metric{u.WAN.ServiceProviderASN, gauge, details.ServiceProvider.ASN.Val, providerLabels}) diff --git a/pkg/promunifi/wan_test.go b/pkg/promunifi/wan_test.go new file mode 100644 index 00000000..acf67747 --- /dev/null +++ b/pkg/promunifi/wan_test.go @@ -0,0 +1,69 @@ +//nolint:testpackage // white-box: exercises the unexported descriptors and export path. +package promunifi + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/unpoller/unifi/v6" +) + +func testWANConfig() *unifi.WANEnrichedConfiguration { + return &unifi.WANEnrichedConfiguration{ + SiteName: "Default (default)", + SourceName: "https://udr.example", + Configuration: unifi.WANConfiguration{ + ID: "a1", + Name: "Internet 1", + WANNetworkgroup: "WAN", + WANType: "dhcp", + WANLoadBalanceType: "weighted", + }, + Details: unifi.WANDetails{ + ServiceProvider: unifi.WANServiceProvider{ + Name: "SpaceX Starlink", + City: "Brussels", + }, + }, + } +} + +// TestExportWANIsAttributed is the point of this change. Every unpoller_wan_* +// series used to ship with empty site_name and source, which makes the metrics +// of two controllers polled by the same instance indistinguishable. The only +// way to attribute them downstream was to hardcode a mapping in the scrape +// config and hope no second controller ever gained a gateway. +func TestExportWANIsAttributed(t *testing.T) { + t.Parallel() + + r := &fakeReport{} + u := &promUnifi{WAN: descWAN("unifi_")} + u.exportWAN(r, testWANConfig()) + + require.NotEmpty(t, r.sent, "a WAN configuration must produce metrics") + + a := assert.New(t) + + for _, m := range r.sent { + require.GreaterOrEqual(t, len(m.Labels), 7, "every WAN metric carries the base label set") + // site_name and source are the last two of the base label set; the + // provider descriptors substitute isp_name/isp_city earlier but keep + // the same trailing pair. + a.Equal("Default (default)", m.Labels[len(m.Labels)-2], "site_name must be populated") + a.Equal("https://udr.example", m.Labels[len(m.Labels)-1], "source must be populated") + a.NotContains(m.Labels[:len(m.Labels)-2], "", "no other label should be blank in this fixture") + } +} + +// TestExportWANNilIsSafe pins the existing guard: the collector iterates over +// whatever the input plugin produced, and a nil entry must not panic a poll. +func TestExportWANNilIsSafe(t *testing.T) { + t.Parallel() + + r := &fakeReport{} + u := &promUnifi{WAN: descWAN("unifi_")} + u.exportWAN(r, nil) + + assert.Empty(t, r.sent, "a nil WAN configuration produces nothing and does not panic") +}