Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPv6 Support with single stack networks #544

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package migrations

import (
r "gopkg.in/rethinkdb/rethinkdb-go.v6"

"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/metal"
)

func init() {
type tmpPartition struct {
PrivateNetworkPrefixLength uint8 `rethinkdb:"privatenetworkprefixlength"`
}
datastore.MustRegisterMigration(datastore.Migration{
Name: "migrate partition.childprefixlength to tenant super network",
Version: 6,
Up: func(db *r.Term, session r.QueryExecutor, rs *datastore.RethinkStore) error {
nws, err := rs.ListNetworks()
if err != nil {
return err
}

for _, old := range nws {
cursor, err := db.Table("partition").Get(old.PartitionID).Run(session)
if err != nil {
return err
}
var partition tmpPartition
err = cursor.One(&partition)
if err != nil {
return err
}

// TODO: does not work somehow
new := old

af, err := metal.GetAddressFamily(new.Prefixes)
if err != nil {
return err
}
if af != nil {
new.AddressFamily = *af
}
if new.PrivateSuper {
new.DefaultChildPrefixLength = &partition.PrivateNetworkPrefixLength
}
err = rs.UpdateNetwork(&old, &new)
if err != nil {
return err
}
err = cursor.Close()
if err != nil {
return err
}
}

_, err = db.Table("partition").Replace(r.Row.Without("privatenetworkprefixlength")).RunWrite(session)
return err
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package migrations_integration

import (
"context"
"fmt"
"log/slog"
"os"
"time"
Expand All @@ -15,14 +16,15 @@ import (
_ "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore/migrations"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/metal"
"github.com/metal-stack/metal-api/test"
r "gopkg.in/rethinkdb/rethinkdb-go.v6"

"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_Migration(t *testing.T) {
func Test_MigrationProvisioningEventContainer(t *testing.T) {
container, c, err := test.StartRethink(t)
require.NoError(t, err)
defer func() {
Expand Down Expand Up @@ -125,3 +127,110 @@ func Test_Migration(t *testing.T) {
assert.Equal(t, ec.Events[0].Time.Unix(), lastEventTime.Unix())
assert.Equal(t, ec.Events[1].Time.Unix(), now.Unix())
}

func Test_MigrationChildPrefixLength(t *testing.T) {
type tmpPartition struct {
ID string `rethinkdb:"id"`
PrivateNetworkPrefixLength uint8 `rethinkdb:"privatenetworkprefixlength"`
}

container, c, err := test.StartRethink(t)
require.NoError(t, err)
defer func() {
_ = container.Terminate(context.Background())
}()

log := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))

rs := datastore.New(log, c.IP+":"+c.Port, c.DB, c.User, c.Password)
// limit poolsize to speed up initialization
rs.VRFPoolRangeMin = 10000
rs.VRFPoolRangeMax = 10010
rs.ASNPoolRangeMin = 10000
rs.ASNPoolRangeMax = 10010

err = rs.Connect()
require.NoError(t, err)
err = rs.Initialize()
require.NoError(t, err)

var (
p1 = &tmpPartition{
ID: "p1",
PrivateNetworkPrefixLength: 22,
}
p2 = &tmpPartition{
ID: "p2",
PrivateNetworkPrefixLength: 24,
}
n1 = &metal.Network{
Base: metal.Base{
ID: "n1",
},
PartitionID: "p1",
Prefixes: metal.Prefixes{
{IP: "10.0.0.0", Length: "8"},
},
PrivateSuper: true,
}
n2 = &metal.Network{
Base: metal.Base{
ID: "n2",
},
Prefixes: metal.Prefixes{
{IP: "2001::", Length: "64"},
},
PartitionID: "p2",
PrivateSuper: true,
}
n3 = &metal.Network{
Base: metal.Base{
ID: "n3",
},
Prefixes: metal.Prefixes{
{IP: "100.1.0.0", Length: "22"},
},
PartitionID: "p2",
PrivateSuper: false,
}
)
_, err = r.DB("metal").Table("partition").Insert(p1).RunWrite(rs.Session())
require.NoError(t, err)
_, err = r.DB("metal").Table("partition").Insert(p2).RunWrite(rs.Session())
require.NoError(t, err)

err = rs.CreateNetwork(n1)
require.NoError(t, err)
err = rs.CreateNetwork(n2)
require.NoError(t, err)
err = rs.CreateNetwork(n3)
require.NoError(t, err)

err = rs.Migrate(nil, false)
require.NoError(t, err)

p, err := rs.FindPartition(p1.ID)
require.NoError(t, err)
require.NotNil(t, p)
p, err = rs.FindPartition(p2.ID)
require.NoError(t, err)
require.NotNil(t, p)

n1fetched, err := rs.FindNetworkByID(n1.ID)
require.NoError(t, err)
require.NotNil(t, n1fetched)
require.Equal(t, p1.PrivateNetworkPrefixLength, *n1fetched.DefaultChildPrefixLength, fmt.Sprintf("childprefixlength:%d", *n1fetched.DefaultChildPrefixLength))
require.Equal(t, metal.IPv4AddressFamily, n1fetched.AddressFamily)

n2fetched, err := rs.FindNetworkByID(n2.ID)
require.NoError(t, err)
require.NotNil(t, n2fetched)
require.Equal(t, p2.PrivateNetworkPrefixLength, *n2fetched.DefaultChildPrefixLength, fmt.Sprintf("childprefixlength:%d", *n2fetched.DefaultChildPrefixLength))
require.Equal(t, metal.IPv6AddressFamily, n2fetched.AddressFamily)

n3fetched, err := rs.FindNetworkByID(n3.ID)
require.NoError(t, err)
require.NotNil(t, n3fetched)
require.Nil(t, n3fetched.DefaultChildPrefixLength)
require.Equal(t, metal.IPv4AddressFamily, n3fetched.AddressFamily)
}
31 changes: 19 additions & 12 deletions cmd/metal-api/internal/datastore/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@ import (

// NetworkSearchQuery can be used to search networks.
type NetworkSearchQuery struct {
ID *string `json:"id" optional:"true"`
Name *string `json:"name" optional:"true"`
PartitionID *string `json:"partitionid" optional:"true"`
ProjectID *string `json:"projectid" optional:"true"`
Prefixes []string `json:"prefixes" optional:"true"`
DestinationPrefixes []string `json:"destinationprefixes" optional:"true"`
Nat *bool `json:"nat" optional:"true"`
PrivateSuper *bool `json:"privatesuper" optional:"true"`
Underlay *bool `json:"underlay" optional:"true"`
Vrf *int64 `json:"vrf" optional:"true"`
ParentNetworkID *string `json:"parentnetworkid" optional:"true"`
Labels map[string]string `json:"labels" optional:"true"`
ID *string `json:"id" optional:"true"`
Name *string `json:"name" optional:"true"`
PartitionID *string `json:"partitionid" optional:"true"`
ProjectID *string `json:"projectid" optional:"true"`
Prefixes []string `json:"prefixes" optional:"true"`
DestinationPrefixes []string `json:"destinationprefixes" optional:"true"`
Nat *bool `json:"nat" optional:"true"`
PrivateSuper *bool `json:"privatesuper" optional:"true"`
Underlay *bool `json:"underlay" optional:"true"`
Vrf *int64 `json:"vrf" optional:"true"`
ParentNetworkID *string `json:"parentnetworkid" optional:"true"`
Labels map[string]string `json:"labels" optional:"true"`
AddressFamily *metal.AddressFamily `json:"addressfamily" optional:"true"`
}

func (p *NetworkSearchQuery) Validate() error {
Expand Down Expand Up @@ -104,6 +105,12 @@ func (p *NetworkSearchQuery) generateTerm(rs *RethinkStore) (*r.Term, error) {
})
}

if p.AddressFamily != nil {
q = q.Filter(func(row r.Term) r.Term {
return row.Field("addressfamily").Eq(string(*p.AddressFamily))
})
}

for k, v := range p.Labels {
k := k
v := v
Expand Down
7 changes: 6 additions & 1 deletion cmd/metal-api/internal/datastore/rethinkdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ func New(log *slog.Logger, dbhost string, dbname string, dbuser string, dbpass s
}
}

// Session exported for migration unit test
func (rs *RethinkStore) Session() r.QueryExecutor {
return rs.session
}

func multi(session r.QueryExecutor, tt ...r.Term) error {
for _, t := range tt {
if err := t.Exec(session); err != nil {
Expand Down Expand Up @@ -373,7 +378,7 @@ func (rs *RethinkStore) findEntity(query *r.Term, entity interface{}) error {
}
defer res.Close()
if res.IsNil() {
return metal.NotFound("no %v with found", getEntityName(entity))
return metal.NotFound("no %v found", getEntityName(entity))
}

hasResult := res.Next(entity)
Expand Down
65 changes: 54 additions & 11 deletions cmd/metal-api/internal/metal/network.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package metal

import (
"fmt"
"net"
"net/netip"
"strconv"

"github.com/metal-stack/metal-lib/pkg/pointer"
"github.com/samber/lo"
)

Expand Down Expand Up @@ -207,17 +209,40 @@ func (p *Prefix) equals(other *Prefix) bool {
// TODO specify rethinkdb restrictions.
type Network struct {
Base
Prefixes Prefixes `rethinkdb:"prefixes" json:"prefixes"`
DestinationPrefixes Prefixes `rethinkdb:"destinationprefixes" json:"destinationprefixes"`
PartitionID string `rethinkdb:"partitionid" json:"partitionid"`
ProjectID string `rethinkdb:"projectid" json:"projectid"`
ParentNetworkID string `rethinkdb:"parentnetworkid" json:"parentnetworkid"`
Vrf uint `rethinkdb:"vrf" json:"vrf"`
PrivateSuper bool `rethinkdb:"privatesuper" json:"privatesuper"`
Nat bool `rethinkdb:"nat" json:"nat"`
Underlay bool `rethinkdb:"underlay" json:"underlay"`
Shared bool `rethinkdb:"shared" json:"shared"`
Labels map[string]string `rethinkdb:"labels" json:"labels"`
Prefixes Prefixes `rethinkdb:"prefixes" json:"prefixes"`
DestinationPrefixes Prefixes `rethinkdb:"destinationprefixes" json:"destinationprefixes"`
DefaultChildPrefixLength *uint8 `rethinkdb:"defaultchildprefixlength" json:"childprefixlength" description:"if privatesuper, this defines the bitlen of child prefixes if not nil"`
PartitionID string `rethinkdb:"partitionid" json:"partitionid"`
ProjectID string `rethinkdb:"projectid" json:"projectid"`
ParentNetworkID string `rethinkdb:"parentnetworkid" json:"parentnetworkid"`
Vrf uint `rethinkdb:"vrf" json:"vrf"`
PrivateSuper bool `rethinkdb:"privatesuper" json:"privatesuper"`
Nat bool `rethinkdb:"nat" json:"nat"`
Underlay bool `rethinkdb:"underlay" json:"underlay"`
Shared bool `rethinkdb:"shared" json:"shared"`
Labels map[string]string `rethinkdb:"labels" json:"labels"`
AddressFamily AddressFamily `rethinkdb:"addressfamily" json:"addressfamily"`
}

// AddressFamily identifies IPv4/IPv6
type AddressFamily string

const (
// IPv4AddressFamily identifies IPv4
IPv4AddressFamily = AddressFamily("IPv4")
// IPv6AddressFamily identifies IPv6
IPv6AddressFamily = AddressFamily("IPv6")
)

// ToAddressFamily will convert a string af to a AddressFamily
func ToAddressFamily(af string) AddressFamily {
switch af {
case "IPv4", "ipv4":
return IPv4AddressFamily
case "IPv6", "ipv6":
return IPv6AddressFamily
}
return IPv4AddressFamily
}

// Networks is a list of networks.
Expand Down Expand Up @@ -323,3 +348,21 @@ func (nics Nics) ByIdentifier() map[string]*Nic {

return res
}

func GetAddressFamily(prefixes Prefixes) (*AddressFamily, error) {
if len(prefixes) == 0 {
return nil, nil
}

parsed, err := netip.ParsePrefix(prefixes[0].String())
if err != nil {
return nil, err
}
if parsed.Addr().Is4() {
return pointer.Pointer(IPv4AddressFamily), nil
}
if parsed.Addr().Is6() {
return pointer.Pointer(IPv6AddressFamily), nil
}
return nil, fmt.Errorf("unable to detect addressfamily from prefixes:%v", prefixes)
}
Loading