Add Floating IP scanning and a durable address registry with configurable history depth

Adds POST /api/v1/admin/ips/scan (plus an optional periodic ticker) to
discover free Floating IPs in the OpenStack project and feed them straight
into the check queue. More importantly, decouples check/event history from
ip_queue's lifecycle: a new ip_registry table (migration 0007) gives every
address ever submitted a durable identity, so deleting it from the queue no
longer destroys its history — it's still reachable via the new
GET /api/v1/admin/registry[/{ip}] endpoints and the dashboard's /registry
pages, with retention depth configurable in check cycles per address
(history_retention_cycles, 0 = unlimited).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ayurishchevandClaude Sonnet 5 committed 2026-09-23 09:52:01 +03:00
1 parent 78b20fa5be
commit 582b44f314
47 files changed
+1781 -107

No files matched your search

+16
View File
@@ -150,6 +150,22 @@ func (c *Client) GetFloatingIPByAddress(ctx context.Context, address string) (*F
return &FloatingIP{ID: f.ID, Address: f.FloatingIP, PortID: f.PortID, ProjectID: f.TenantID}, nil
}
func (c *Client) ListFloatingIPs(ctx context.Context) ([]FloatingIP, error) {
pages, err := floatingips.List(c.networking, floatingips.ListOpts{}).AllPages(ctx)
if err != nil {
return nil, fmt.Errorf("openstack: list floating ips: %w", err)
}
list, err := floatingips.ExtractFloatingIPs(pages)
if err != nil {
return nil, fmt.Errorf("openstack: extract floating ips: %w", err)
}
out := make([]FloatingIP, 0, len(list))
for _, f := range list {
out = append(out, FloatingIP{ID: f.ID, Address: f.FloatingIP, PortID: f.PortID, ProjectID: f.TenantID})
}
return out, nil
}
func (c *Client) AssociateFloatingIP(ctx context.Context, fipID, portID string) error {
_, err := floatingips.Update(ctx, c.networking, fipID, floatingips.UpdateOpts{
PortID: &portID,
+6
View File
@@ -24,6 +24,12 @@ type FloatingIPClient interface {
// is registered in the service project.
GetFloatingIPByAddress(ctx context.Context, address string) (*FloatingIP, error)
// ListFloatingIPs returns every floating IP registered in the service
// project, both associated and free — filtering to the free pool (empty
// PortID) is the caller's job. Used to discover addresses to feed into
// the check queue without an admin having to enumerate them by hand.
ListFloatingIPs(ctx context.Context) ([]FloatingIP, error)
// AssociateFloatingIP attaches the floating IP to the given Neutron
// port (the validator's primary NIC port).
AssociateFloatingIP(ctx context.Context, fipID, portID string) error
+10
View File
@@ -56,6 +56,16 @@ func (m *MockClient) GetFloatingIPByAddress(ctx context.Context, address string)
return &f, nil
}
func (m *MockClient) ListFloatingIPs(ctx context.Context) ([]FloatingIP, error) {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]FloatingIP, 0, len(m.fips))
for _, f := range m.fips {
out = append(out, *f)
}
return out, nil
}
func (m *MockClient) AssociateFloatingIP(ctx context.Context, fipID, portID string) error {
m.mu.Lock()
defer m.mu.Unlock()