feat(13-01): add DeviceInterfaceEvent publisher and wire into PollDevice
- DeviceInterfaceEvent type publishes to device.interfaces.{device_id}
- PublishDeviceInterfaces method follows existing publisher pattern
- DEVICE_EVENTS stream includes device.interfaces.> subject
- PollDevice collects interface info after traffic counters, before health
- Non-fatal errors with Prometheus metrics for publish success/failure
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -132,6 +132,7 @@ func NewPublisher(natsURL string) (*Publisher, error) {
|
||||
Subjects: []string{
|
||||
"device.status.>",
|
||||
"device.metrics.>",
|
||||
"device.interfaces.>",
|
||||
"device.firmware.>",
|
||||
"device.credential_changed.>",
|
||||
"config.changed.>",
|
||||
@@ -226,6 +227,44 @@ func (p *Publisher) PublishMetrics(ctx context.Context, event DeviceMetricsEvent
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeviceInterfaceEvent is the payload published to the DEVICE_EVENTS NATS stream
|
||||
// when interface identity data (name, MAC, type, running) is collected from a
|
||||
// device. The link discovery system uses MAC addresses to resolve which managed
|
||||
// device owns each end of a wireless link.
|
||||
type DeviceInterfaceEvent struct {
|
||||
DeviceID string `json:"device_id"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
CollectedAt string `json:"collected_at"` // RFC3339
|
||||
Interfaces []device.InterfaceInfo `json:"interfaces"`
|
||||
}
|
||||
|
||||
// PublishDeviceInterfaces publishes interface identity data to the DEVICE_EVENTS
|
||||
// NATS stream for link discovery.
|
||||
//
|
||||
// Events are published to "device.interfaces.{device_id}" so consumers can
|
||||
// subscribe to all interface data or filter by device.
|
||||
func (p *Publisher) PublishDeviceInterfaces(ctx context.Context, event DeviceInterfaceEvent) error {
|
||||
data, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshalling device interface event: %w", err)
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("device.interfaces.%s", event.DeviceID)
|
||||
|
||||
_, err = p.js.Publish(ctx, subject, data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("publishing to %s: %w", subject, err)
|
||||
}
|
||||
|
||||
slog.Debug("published device interface event",
|
||||
"device_id", event.DeviceID,
|
||||
"interfaces", len(event.Interfaces),
|
||||
"subject", subject,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PublishWirelessRegistrations publishes per-client wireless registration data
|
||||
// and RF monitor stats to the WIRELESS_REGISTRATIONS NATS stream.
|
||||
//
|
||||
|
||||
@@ -326,6 +326,29 @@ func PollDevice(
|
||||
observability.NATSPublishTotal.WithLabelValues("metrics", "success").Inc()
|
||||
}
|
||||
|
||||
// Interface identity data for link discovery (MAC addresses, types).
|
||||
cmdCtx, cmdCancel = context.WithTimeout(ctx, cmdTimeout)
|
||||
ifaceInfo, err := withTimeout[[]device.InterfaceInfo](cmdCtx, func() ([]device.InterfaceInfo, error) {
|
||||
return device.CollectInterfaceInfo(client)
|
||||
})
|
||||
cmdCancel()
|
||||
if err != nil {
|
||||
slog.Warn("failed to collect interface info", "device_id", dev.ID, "error", err)
|
||||
}
|
||||
if len(ifaceInfo) > 0 {
|
||||
if pubErr := pub.PublishDeviceInterfaces(ctx, bus.DeviceInterfaceEvent{
|
||||
DeviceID: dev.ID,
|
||||
TenantID: dev.TenantID,
|
||||
CollectedAt: collectedAt,
|
||||
Interfaces: ifaceInfo,
|
||||
}); pubErr != nil {
|
||||
slog.Warn("failed to publish device interfaces", "device_id", dev.ID, "error", pubErr)
|
||||
observability.NATSPublishTotal.WithLabelValues("interfaces_info", "error").Inc()
|
||||
} else {
|
||||
observability.NATSPublishTotal.WithLabelValues("interfaces_info", "success").Inc()
|
||||
}
|
||||
}
|
||||
|
||||
// System health (CPU, memory, disk, temperature).
|
||||
cmdCtx, cmdCancel = context.WithTimeout(ctx, cmdTimeout)
|
||||
health, err := withTimeout[device.HealthMetrics](cmdCtx, func() (device.HealthMetrics, error) {
|
||||
|
||||
Reference in New Issue
Block a user