Repository: juanfont/headscale

Issue:
TestShuffleDERPMapDeterministic is flaky
TestShuffleDERPMapDeterministic is suppose to use the fixed seed based on a base name, but the test is flaky on CI (not locally). 

This is getting annoying and needs to be investigated.

```
=== FAIL: hscontrol/derp TestShuffleDERPMapDeterministic/multiple_regions_with_nodes (0.00s)
    derp_test.go:184: Shuffled DERP map doesn't match expected (-expected +actual):
          &tailcfg.DERPMap{
          	HomeParams: nil,
          	Regions: map[int]*tailcfg.DERPRegion{
          		2: &{
          			... // 5 identical fields
          			Avoid:           false,
          			NoMeasureNoHome: false,
          			Nodes: []*tailcfg.DERPNode{
          				&{
        - 					Name:     "2f",
        + 					Name:     "2d",
          					RegionID: 2,
        - 					HostName: "derp2f.tailscale.com",
        + 					HostName: "derp2d.tailscale.com",
          					CertName: "",
          					IPv4:     "",
          					... // 7 identical fields
          				},
          				&{Name: "2e", RegionID: 2, HostName: "derp2e.tailscale.com"},
          				&{
        - 					Name:     "2d",
        + 					Name:     "2f",
          					RegionID: 2,
        - 					HostName: "derp2d.tailscale.com",
        + 					HostName: "derp2f.tailscale.com",
          					CertName: "",
          					IPv4:     "",
          					... // 7 identical fields
          				},
          			},
          		},
          		10: &{
          			... // 5 identical fields
          			Avoid:           false,
          			NoMeasureNoHome: false,
          			Nodes: []*tailcfg.DERPNode{
          				&{
        - 					Name:     "10b",
        + 					Name:     "10d",
          					RegionID: 10,
        - 					HostName: "derp10b.tailscale.com",
        + 					HostName: "derp10d.tailscale.com",
          					CertName: "",
          					IPv4:     "",
          					... // 7 identical fields
          				},
          				&{Name: "10c", RegionID: 10, HostName: "derp10c.tailscale.com"},
          				&{
        - 					Name:     "10d",
        + 					Name:     "10b",
          					RegionID: 10,
        - 					HostName: "derp10d.tailscale.com",
        + 					HostName: "derp10b.tailscale.com",
          					CertName: "",
          					IPv4:     "",
          					... // 7 identical fields
          				},
          			},
          		},
          	},
          	OmitDefaultRegions: false,
          }
```

Discussion:

Solution patch:
diff --git a/hscontrol/derp/derp.go b/hscontrol/derp/derp.go
index 6c8244f5..42d74abe 100644
--- a/hscontrol/derp/derp.go
+++ b/hscontrol/derp/derp.go
@@ -12,6 +12,7 @@ import (
 	"net/url"
 	"os"
 	"reflect"
+	"slices"
 	"sync"
 	"time"
 
@@ -126,7 +127,17 @@ func shuffleDERPMap(dm *tailcfg.DERPMap) {
 		return
 	}
 
-	for id, region := range dm.Regions {
+	// Collect region IDs and sort them to ensure deterministic iteration order.
+	// Map iteration order is non-deterministic in Go, which would cause the
+	// shuffle to be non-deterministic even with a fixed seed.
+	ids := make([]int, 0, len(dm.Regions))
+	for id := range dm.Regions {
+		ids = append(ids, id)
+	}
+	slices.Sort(ids)
+
+	for _, id := range ids {
+		region := dm.Regions[id]
 		if len(region.Nodes) == 0 {
 			continue
 		}



Test patch:
diff --git a/hscontrol/derp/derp_test.go b/hscontrol/derp/derp_test.go
index 9334de05..91d605a6 100644
--- a/hscontrol/derp/derp_test.go
+++ b/hscontrol/derp/derp_test.go
@@ -83,9 +83,9 @@ func TestShuffleDERPMapDeterministic(t *testing.T) {
 						RegionCode: "sea",
 						RegionName: "Seattle",
 						Nodes: []*tailcfg.DERPNode{
-							{Name: "10b", RegionID: 10, HostName: "derp10b.tailscale.com"},
-							{Name: "10c", RegionID: 10, HostName: "derp10c.tailscale.com"},
 							{Name: "10d", RegionID: 10, HostName: "derp10d.tailscale.com"},
+							{Name: "10c", RegionID: 10, HostName: "derp10c.tailscale.com"},
+							{Name: "10b", RegionID: 10, HostName: "derp10b.tailscale.com"},
 						},
 					},
 					2: {
@@ -93,9 +93,9 @@ func TestShuffleDERPMapDeterministic(t *testing.T) {
 						RegionCode: "sfo",
 						RegionName: "San Francisco",
 						Nodes: []*tailcfg.DERPNode{
-							{Name: "2f", RegionID: 2, HostName: "derp2f.tailscale.com"},
-							{Name: "2e", RegionID: 2, HostName: "derp2e.tailscale.com"},
 							{Name: "2d", RegionID: 2, HostName: "derp2d.tailscale.com"},
+							{Name: "2e", RegionID: 2, HostName: "derp2e.tailscale.com"},
+							{Name: "2f", RegionID: 2, HostName: "derp2f.tailscale.com"},
 						},
 					},
 				},
@@ -169,6 +169,74 @@ func TestShuffleDERPMapDeterministic(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:       "same dataset with another base domain",
+			baseDomain: "another.example.com",
+			derpMap: &tailcfg.DERPMap{
+				Regions: map[int]*tailcfg.DERPRegion{
+					4: {
+						RegionID:   4,
+						RegionCode: "fra",
+						RegionName: "Frankfurt",
+						Nodes: []*tailcfg.DERPNode{
+							{Name: "4f", RegionID: 4, HostName: "derp4f.tailscale.com"},
+							{Name: "4g", RegionID: 4, HostName: "derp4g.tailscale.com"},
+							{Name: "4h", RegionID: 4, HostName: "derp4h.tailscale.com"},
+							{Name: "4i", RegionID: 4, HostName: "derp4i.tailscale.com"},
+						},
+					},
+				},
+			},
+			expected: &tailcfg.DERPMap{
+				Regions: map[int]*tailcfg.DERPRegion{
+					4: {
+						RegionID:   4,
+						RegionCode: "fra",
+						RegionName: "Frankfurt",
+						Nodes: []*tailcfg.DERPNode{
+							{Name: "4h", RegionID: 4, HostName: "derp4h.tailscale.com"},
+							{Name: "4f", RegionID: 4, HostName: "derp4f.tailscale.com"},
+							{Name: "4g", RegionID: 4, HostName: "derp4g.tailscale.com"},
+							{Name: "4i", RegionID: 4, HostName: "derp4i.tailscale.com"},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:       "same dataset with yet another base domain",
+			baseDomain: "yetanother.example.com",
+			derpMap: &tailcfg.DERPMap{
+				Regions: map[int]*tailcfg.DERPRegion{
+					4: {
+						RegionID:   4,
+						RegionCode: "fra",
+						RegionName: "Frankfurt",
+						Nodes: []*tailcfg.DERPNode{
+							{Name: "4f", RegionID: 4, HostName: "derp4f.tailscale.com"},
+							{Name: "4g", RegionID: 4, HostName: "derp4g.tailscale.com"},
+							{Name: "4h", RegionID: 4, HostName: "derp4h.tailscale.com"},
+							{Name: "4i", RegionID: 4, HostName: "derp4i.tailscale.com"},
+						},
+					},
+				},
+			},
+			expected: &tailcfg.DERPMap{
+				Regions: map[int]*tailcfg.DERPRegion{
+					4: {
+						RegionID:   4,
+						RegionCode: "fra",
+						RegionName: "Frankfurt",
+						Nodes: []*tailcfg.DERPNode{
+							{Name: "4i", RegionID: 4, HostName: "derp4i.tailscale.com"},
+							{Name: "4h", RegionID: 4, HostName: "derp4h.tailscale.com"},
+							{Name: "4f", RegionID: 4, HostName: "derp4f.tailscale.com"},
+							{Name: "4g", RegionID: 4, HostName: "derp4g.tailscale.com"},
+						},
+					},
+				},
+			},
+		},
 	}
 
 	for _, tt := range tests {