Migrate IP fields in database to dedicated columns (#1869)
This commit is contained in:
parent
85cef84e17
commit
2ce23df45a
39 changed files with 1885 additions and 1055 deletions
|
@ -1,49 +1,41 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
)
|
||||
|
||||
func TestIPAllocator(t *testing.T) {
|
||||
mpp := func(pref string) netip.Prefix {
|
||||
return netip.MustParsePrefix(pref)
|
||||
}
|
||||
na := func(pref string) netip.Addr {
|
||||
return netip.MustParseAddr(pref)
|
||||
}
|
||||
newDb := func() *HSDatabase {
|
||||
tmpDir, err := os.MkdirTemp("", "headscale-db-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("creating temp dir: %s", err)
|
||||
}
|
||||
db, _ = NewHeadscaleDatabase(
|
||||
types.DatabaseConfig{
|
||||
Type: "sqlite3",
|
||||
Sqlite: types.SqliteConfig{
|
||||
Path: tmpDir + "/headscale_test.db",
|
||||
},
|
||||
},
|
||||
"",
|
||||
)
|
||||
|
||||
return db
|
||||
}
|
||||
var mpp = func(pref string) *netip.Prefix {
|
||||
p := netip.MustParsePrefix(pref)
|
||||
return &p
|
||||
}
|
||||
var na = func(pref string) netip.Addr {
|
||||
return netip.MustParseAddr(pref)
|
||||
}
|
||||
var nap = func(pref string) *netip.Addr {
|
||||
n := na(pref)
|
||||
return &n
|
||||
}
|
||||
|
||||
func TestIPAllocatorSequential(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dbFunc func() *HSDatabase
|
||||
|
||||
prefix4 netip.Prefix
|
||||
prefix6 netip.Prefix
|
||||
prefix4 *netip.Prefix
|
||||
prefix6 *netip.Prefix
|
||||
getCount int
|
||||
want []types.NodeAddresses
|
||||
want4 []netip.Addr
|
||||
want6 []netip.Addr
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
|
@ -56,23 +48,49 @@ func TestIPAllocator(t *testing.T) {
|
|||
|
||||
getCount: 1,
|
||||
|
||||
want: []types.NodeAddresses{
|
||||
{
|
||||
na("100.64.0.1"),
|
||||
na("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
want4: []netip.Addr{
|
||||
na("100.64.0.1"),
|
||||
},
|
||||
want6: []netip.Addr{
|
||||
na("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple-v4",
|
||||
dbFunc: func() *HSDatabase {
|
||||
return nil
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
|
||||
getCount: 1,
|
||||
|
||||
want4: []netip.Addr{
|
||||
na("100.64.0.1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple-v6",
|
||||
dbFunc: func() *HSDatabase {
|
||||
return nil
|
||||
},
|
||||
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
getCount: 1,
|
||||
|
||||
want6: []netip.Addr{
|
||||
na("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple-with-db",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := newDb()
|
||||
db := dbForTest(t, "simple-with-db")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPAddresses: types.NodeAddresses{
|
||||
na("100.64.0.1"),
|
||||
na("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
IPv4: nap("100.64.0.1"),
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
})
|
||||
|
||||
return db
|
||||
|
@ -83,23 +101,21 @@ func TestIPAllocator(t *testing.T) {
|
|||
|
||||
getCount: 1,
|
||||
|
||||
want: []types.NodeAddresses{
|
||||
{
|
||||
na("100.64.0.2"),
|
||||
na("fd7a:115c:a1e0::2"),
|
||||
},
|
||||
want4: []netip.Addr{
|
||||
na("100.64.0.2"),
|
||||
},
|
||||
want6: []netip.Addr{
|
||||
na("fd7a:115c:a1e0::2"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "before-after-free-middle-in-db",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := newDb()
|
||||
db := dbForTest(t, "before-after-free-middle-in-db")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPAddresses: types.NodeAddresses{
|
||||
na("100.64.0.2"),
|
||||
na("fd7a:115c:a1e0::2"),
|
||||
},
|
||||
IPv4: nap("100.64.0.2"),
|
||||
IPv6: nap("fd7a:115c:a1e0::2"),
|
||||
})
|
||||
|
||||
return db
|
||||
|
@ -110,15 +126,13 @@ func TestIPAllocator(t *testing.T) {
|
|||
|
||||
getCount: 2,
|
||||
|
||||
want: []types.NodeAddresses{
|
||||
{
|
||||
na("100.64.0.1"),
|
||||
na("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
{
|
||||
na("100.64.0.3"),
|
||||
na("fd7a:115c:a1e0::3"),
|
||||
},
|
||||
want4: []netip.Addr{
|
||||
na("100.64.0.1"),
|
||||
na("100.64.0.3"),
|
||||
},
|
||||
want6: []netip.Addr{
|
||||
na("fd7a:115c:a1e0::1"),
|
||||
na("fd7a:115c:a1e0::3"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -127,24 +141,347 @@ func TestIPAllocator(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := tt.dbFunc()
|
||||
|
||||
alloc, _ := NewIPAllocator(db, tt.prefix4, tt.prefix6)
|
||||
alloc, _ := NewIPAllocator(
|
||||
db,
|
||||
tt.prefix4,
|
||||
tt.prefix6,
|
||||
types.IPAllocationStrategySequential,
|
||||
)
|
||||
|
||||
spew.Dump(alloc)
|
||||
|
||||
t.Logf("prefixes: %q, %q", tt.prefix4.String(), tt.prefix6.String())
|
||||
|
||||
var got []types.NodeAddresses
|
||||
var got4s []netip.Addr
|
||||
var got6s []netip.Addr
|
||||
|
||||
for range tt.getCount {
|
||||
gotSet, err := alloc.Next()
|
||||
got4, got6, err := alloc.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("allocating next IP: %s", err)
|
||||
}
|
||||
|
||||
got = append(got, gotSet)
|
||||
if got4 != nil {
|
||||
got4s = append(got4s, *got4)
|
||||
}
|
||||
|
||||
if got6 != nil {
|
||||
got6s = append(got6s, *got6)
|
||||
}
|
||||
}
|
||||
if diff := cmp.Diff(tt.want, got, util.Comparers...); diff != "" {
|
||||
t.Errorf("IPAllocator unexpected result (-want +got):\n%s", diff)
|
||||
if diff := cmp.Diff(tt.want4, got4s, util.Comparers...); diff != "" {
|
||||
t.Errorf("IPAllocator 4s unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.want6, got6s, util.Comparers...); diff != "" {
|
||||
t.Errorf("IPAllocator 6s unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPAllocatorRandom(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dbFunc func() *HSDatabase
|
||||
|
||||
getCount int
|
||||
|
||||
prefix4 *netip.Prefix
|
||||
prefix6 *netip.Prefix
|
||||
want4 bool
|
||||
want6 bool
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
dbFunc: func() *HSDatabase {
|
||||
return nil
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
getCount: 1,
|
||||
|
||||
want4: true,
|
||||
want6: true,
|
||||
},
|
||||
{
|
||||
name: "simple-v4",
|
||||
dbFunc: func() *HSDatabase {
|
||||
return nil
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
|
||||
getCount: 1,
|
||||
|
||||
want4: true,
|
||||
want6: false,
|
||||
},
|
||||
{
|
||||
name: "simple-v6",
|
||||
dbFunc: func() *HSDatabase {
|
||||
return nil
|
||||
},
|
||||
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
getCount: 1,
|
||||
|
||||
want4: false,
|
||||
want6: true,
|
||||
},
|
||||
{
|
||||
name: "generate-lots-of-random",
|
||||
dbFunc: func() *HSDatabase {
|
||||
return nil
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
getCount: 1000,
|
||||
|
||||
want4: true,
|
||||
want6: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := tt.dbFunc()
|
||||
|
||||
alloc, _ := NewIPAllocator(db, tt.prefix4, tt.prefix6, types.IPAllocationStrategyRandom)
|
||||
|
||||
spew.Dump(alloc)
|
||||
|
||||
for range tt.getCount {
|
||||
got4, got6, err := alloc.Next()
|
||||
if err != nil {
|
||||
t.Fatalf("allocating next IP: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("addrs ipv4: %v, ipv6: %v", got4, got6)
|
||||
|
||||
if tt.want4 {
|
||||
if got4 == nil {
|
||||
t.Fatalf("expected ipv4 addr, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
if tt.want6 {
|
||||
if got6 == nil {
|
||||
t.Fatalf("expected ipv4 addr, got nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackfillIPAddresses(t *testing.T) {
|
||||
fullNodeP := func(i int) *types.Node {
|
||||
v4 := fmt.Sprintf("100.64.0.%d", i)
|
||||
v6 := fmt.Sprintf("fd7a:115c:a1e0::%d", i)
|
||||
return &types.Node{
|
||||
IPv4DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: v4,
|
||||
},
|
||||
IPv4: nap(v4),
|
||||
IPv6DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: v6,
|
||||
},
|
||||
IPv6: nap(v6),
|
||||
}
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
dbFunc func() *HSDatabase
|
||||
|
||||
prefix4 *netip.Prefix
|
||||
prefix6 *netip.Prefix
|
||||
want types.Nodes
|
||||
}{
|
||||
{
|
||||
name: "simple-backfill-ipv6",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := dbForTest(t, "simple-backfill-ipv6")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.1"),
|
||||
})
|
||||
|
||||
return db
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
IPv4DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: "100.64.0.1",
|
||||
},
|
||||
IPv4: nap("100.64.0.1"),
|
||||
IPv6DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: "fd7a:115c:a1e0::1",
|
||||
},
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple-backfill-ipv4",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := dbForTest(t, "simple-backfill-ipv4")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
})
|
||||
|
||||
return db
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
IPv4DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: "100.64.0.1",
|
||||
},
|
||||
IPv4: nap("100.64.0.1"),
|
||||
IPv6DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: "fd7a:115c:a1e0::1",
|
||||
},
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple-backfill-remove-ipv6",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := dbForTest(t, "simple-backfill-remove-ipv6")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.1"),
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
})
|
||||
|
||||
return db
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
IPv4DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: "100.64.0.1",
|
||||
},
|
||||
IPv4: nap("100.64.0.1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple-backfill-remove-ipv4",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := dbForTest(t, "simple-backfill-remove-ipv4")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.1"),
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
})
|
||||
|
||||
return db
|
||||
},
|
||||
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
want: types.Nodes{
|
||||
&types.Node{
|
||||
IPv6DatabaseField: sql.NullString{
|
||||
Valid: true,
|
||||
String: "fd7a:115c:a1e0::1",
|
||||
},
|
||||
IPv6: nap("fd7a:115c:a1e0::1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multi-backfill-ipv6",
|
||||
dbFunc: func() *HSDatabase {
|
||||
db := dbForTest(t, "simple-backfill-ipv6")
|
||||
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.1"),
|
||||
})
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.2"),
|
||||
})
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.3"),
|
||||
})
|
||||
db.DB.Save(&types.Node{
|
||||
IPv4: nap("100.64.0.4"),
|
||||
})
|
||||
|
||||
return db
|
||||
},
|
||||
|
||||
prefix4: mpp("100.64.0.0/10"),
|
||||
prefix6: mpp("fd7a:115c:a1e0::/48"),
|
||||
|
||||
want: types.Nodes{
|
||||
fullNodeP(1),
|
||||
fullNodeP(2),
|
||||
fullNodeP(3),
|
||||
fullNodeP(4),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
comps := append(util.Comparers, cmpopts.IgnoreFields(types.Node{},
|
||||
"ID",
|
||||
"MachineKeyDatabaseField",
|
||||
"NodeKeyDatabaseField",
|
||||
"DiscoKeyDatabaseField",
|
||||
"Endpoints",
|
||||
"HostinfoDatabaseField",
|
||||
"Hostinfo",
|
||||
"Routes",
|
||||
"CreatedAt",
|
||||
"UpdatedAt",
|
||||
))
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
db := tt.dbFunc()
|
||||
|
||||
alloc, err := NewIPAllocator(db, tt.prefix4, tt.prefix6, types.IPAllocationStrategySequential)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to set up ip alloc: %s", err)
|
||||
}
|
||||
|
||||
logs, err := db.BackfillNodeIPs(alloc)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to backfill: %s", err)
|
||||
}
|
||||
|
||||
t.Logf("backfill log: \n%s", strings.Join(logs, "\n"))
|
||||
|
||||
got, err := db.ListNodes()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get nodes: %s", err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tt.want, got, comps...); diff != "" {
|
||||
t.Errorf("Backfill unexpected result (-want +got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue