types: move DebugRoutes from routes to types

Unblocks deletion of the routes package.

Updates #3203
This commit is contained in:
Kristoffer Dalby 2026-04-28 13:32:48 +00:00
parent 1fe682b141
commit 942313a10a
4 changed files with 39 additions and 20 deletions

View File

@ -0,0 +1,22 @@
package types
import "net/netip"
// DebugRoutes is the JSON-shaped snapshot of the headscale primary
// route ledger exposed by the /debug/routes endpoint and consumed by
// the integration test harness. It used to live in hscontrol/routes,
// but the algorithm now runs inside hscontrol/state and that package
// must not be imported from integration code.
type DebugRoutes struct {
// AvailableRoutes maps node IDs to their advertised routes
// (intersection of announced and approved). Only nodes currently
// connected to headscale are listed.
AvailableRoutes map[NodeID][]netip.Prefix `json:"available_routes"`
// PrimaryRoutes maps route prefixes to the node currently elected
// primary for that prefix.
PrimaryRoutes map[string]NodeID `json:"primary_routes"`
// UnhealthyNodes lists nodes that have failed health probes.
UnhealthyNodes []NodeID `json:"unhealthy_nodes,omitempty"`
}

View File

@ -6,7 +6,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol"
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
"github.com/juanfont/headscale/hscontrol/routes"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/integration/hsic"
"github.com/ory/dockertest/v3"
@ -43,7 +42,7 @@ type ControlServer interface {
GetIPInNetwork(network *dockertest.Network) string
SetPolicy(pol *policyv2.Policy) error
GetAllMapReponses() (map[types.NodeID][]tailcfg.MapResponse, error)
PrimaryRoutes() (*routes.DebugRoutes, error)
PrimaryRoutes() (*types.DebugRoutes, error)
DebugBatcher() (*hscontrol.DebugBatcherInfo, error)
DebugNodeStore() (map[types.NodeID]types.Node, error)
DebugFilter() ([]tailcfg.FilterRule, error)

View File

@ -27,7 +27,6 @@ import (
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
"github.com/juanfont/headscale/hscontrol"
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
"github.com/juanfont/headscale/hscontrol/routes"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/integration/dockertestutil"
@ -1639,7 +1638,7 @@ func (t *HeadscaleInContainer) GetAllMapReponses() (map[types.NodeID][]tailcfg.M
}
// PrimaryRoutes fetches the primary routes from the debug endpoint.
func (t *HeadscaleInContainer) PrimaryRoutes() (*routes.DebugRoutes, error) {
func (t *HeadscaleInContainer) PrimaryRoutes() (*types.DebugRoutes, error) {
// Execute curl inside the container to access the debug endpoint locally
command := []string{
"curl", "-s", "-H", "Accept: application/json", "http://localhost:9090/debug/routes",
@ -1650,7 +1649,7 @@ func (t *HeadscaleInContainer) PrimaryRoutes() (*routes.DebugRoutes, error) {
return nil, fmt.Errorf("fetching routes from debug endpoint: %w", err)
}
var debugRoutes routes.DebugRoutes
var debugRoutes types.DebugRoutes
if err := json.Unmarshal([]byte(result), &debugRoutes); err != nil { //nolint:noinlineerr
return nil, fmt.Errorf("decoding routes response: %w", err)
}

View File

@ -17,7 +17,6 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
v1 "github.com/juanfont/headscale/gen/go/headscale/v1"
policyv2 "github.com/juanfont/headscale/hscontrol/policy/v2"
"github.com/juanfont/headscale/hscontrol/routes"
"github.com/juanfont/headscale/hscontrol/types"
"github.com/juanfont/headscale/hscontrol/util"
"github.com/juanfont/headscale/integration/hsic"
@ -227,7 +226,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
propagationTime := integrationutil.ScaledTimeout(60 * time.Second)
// Helper function to validate primary routes table state
validatePrimaryRoutes := func(t *testing.T, headscale ControlServer, expectedRoutes *routes.DebugRoutes, message string) {
validatePrimaryRoutes := func(t *testing.T, headscale ControlServer, expectedRoutes *types.DebugRoutes, message string) {
t.Helper()
assert.EventuallyWithT(t, func(c *assert.CollectT) {
primaryRoutesState, err := headscale.PrimaryRoutes()
@ -383,7 +382,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}
// Validate primary routes table state - no routes approved yet
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{},
PrimaryRoutes: map[string]types.NodeID{}, // No primary routes yet
}, "Primary routes table should be empty (no approved routes yet)")
@ -477,7 +476,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute goes through router 1")
// Validate primary routes table state - router 1 is primary
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
// Note: Router 2 and 3 are available but not approved
@ -557,7 +556,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying Router 1 remains PRIMARY after Router 2 approval")
// Validate primary routes table state - router 1 still primary, router 2 approved but standby
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -594,7 +593,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute still goes through router 1 in HA mode")
// Validate primary routes table state - router 1 primary, router 2 approved (standby)
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -704,7 +703,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traffic still flows through PRIMARY router 1 with full HA setup active")
// Validate primary routes table state - all 3 routers approved, router 1 still primary
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -785,7 +784,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute goes through router 2 after failover")
// Validate primary routes table state - router 2 is now primary after router 1 failure
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
// Router 1 is disconnected, so not in AvailableRoutes
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -859,7 +858,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute goes through router 3 after second failover")
// Validate primary routes table state - router 3 is now primary after router 2 failure
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
// Routers 1 and 2 are disconnected, so not in AvailableRoutes
types.NodeID(MustFindNode(subRouter3.Hostname(), nodes).GetId()): {pref},
@ -939,7 +938,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute still goes through router 3 after router 1 recovery")
// Validate primary routes table state - router 3 remains primary after router 1 comes back
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
// Router 2 is still disconnected
@ -1022,7 +1021,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute goes through router 3 after full recovery")
// Validate primary routes table state - router 3 remains primary after all routers back online
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -1109,7 +1108,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute goes through router 1 after route disable")
// Validate primary routes table state - router 1 is primary after router 3 route disabled
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -1197,7 +1196,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute goes through router 2 after second route disable")
// Validate primary routes table state - router 2 is primary after router 1 route disabled
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
// Router 1's route is no longer approved, so not in AvailableRoutes
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -1284,7 +1283,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Verifying traceroute still goes through router 2 after route re-enable")
// Validate primary routes table state after router 1 re-approval
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},
@ -1326,7 +1325,7 @@ func TestHASubnetRouterFailover(t *testing.T) {
}, propagationTime, 200*time.Millisecond, "Waiting for route state after router 3 re-approval")
// Validate primary routes table state after router 3 re-approval
validatePrimaryRoutes(t, headscale, &routes.DebugRoutes{
validatePrimaryRoutes(t, headscale, &types.DebugRoutes{
AvailableRoutes: map[types.NodeID][]netip.Prefix{
types.NodeID(MustFindNode(subRouter1.Hostname(), nodes).GetId()): {pref},
types.NodeID(MustFindNode(subRouter2.Hostname(), nodes).GetId()): {pref},