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

Dualstack Network Support #549

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
5ea7ea6
IPv6 Support
majst01 Jul 4, 2024
22d916e
More tests
majst01 Jul 4, 2024
e8571c9
Validate Prefixes and DestinationPrefixes on create and update network
majst01 Jul 7, 2024
59c7511
Constify defaultChildPrefixlength
majst01 Jul 7, 2024
9f52dfd
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Jul 8, 2024
89a0857
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Jul 9, 2024
aae9274
Support DualStack Networks
majst01 Jul 17, 2024
1b09d5d
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Jul 23, 2024
87a2a98
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Jul 24, 2024
23e4cbd
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Aug 5, 2024
a175469
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Aug 6, 2024
c519d3b
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Aug 7, 2024
a1575ef
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Aug 8, 2024
f553164
Merge master
majst01 Aug 13, 2024
fa47c76
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Aug 14, 2024
6430ea1
Make additional announcable cidrs configurable per tenant super network
majst01 Aug 14, 2024
5f3287e
Merge additionalannouncablecidrs in
majst01 Aug 16, 2024
1822069
Merge master
majst01 Aug 26, 2024
72ac513
satisfy linter
majst01 Aug 26, 2024
82cc73e
Merge master
majst01 Sep 5, 2024
d2cb195
sanitize go.mod
majst01 Sep 5, 2024
d408762
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Sep 10, 2024
8f06d84
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Sep 13, 2024
10f49c7
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Sep 29, 2024
88835d4
Merge branch 'master' into dualstack-support
majst01 Sep 30, 2024
02a518f
Merge master
majst01 Oct 2, 2024
44c8825
Remove false comment
majst01 Oct 2, 2024
fbe6a32
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Oct 2, 2024
7568f8e
Updates
majst01 Oct 7, 2024
9db0e6e
Merge master
majst01 Oct 8, 2024
84c63ad
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Oct 11, 2024
43dc320
Merge branch 'master' into dualstack-support
majst01 Nov 6, 2024
5719737
merge master
majst01 Nov 8, 2024
576df4a
Merge branch 'dualstack-support' of https://github.com/metal-stack/me…
majst01 Nov 8, 2024
2e72212
Introduce a type
majst01 Nov 8, 2024
f2da96e
Merge branch 'master' into dualstack-support
majst01 Nov 11, 2024
bd57dcb
Merge master
majst01 Nov 11, 2024
343ef3b
Merge branch 'master' into dualstack-support
majst01 Nov 11, 2024
f313f41
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Nov 12, 2024
4bb6603
Merge branch 'master' into dualstack-support
majst01 Nov 13, 2024
fbf1970
More consts
majst01 Nov 13, 2024
03c2436
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
majst01 Nov 13, 2024
e726090
Merge branch 'master' into dualstack-support
majst01 Nov 19, 2024
c9c4b7e
Allow update DefaultChildPrefixLength
majst01 Jan 2, 2025
5e73c10
Do not allow mixed AddressFamilies in static firewall rules
majst01 Jan 10, 2025
3b7bb28
Better naming
majst01 Jan 10, 2025
a1a3428
If no Addressfamily is given during allocateIP in a ipv6 only network…
majst01 Jan 10, 2025
391d997
Better error messages on firewall rule validation
majst01 Jan 10, 2025
3fd8505
Simplify addressfamilies
majst01 Jan 13, 2025
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,90 @@
package migrations

import (
"net/netip"

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: 8,
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
}
if cursor.IsNil() {
_ = cursor.Close()
continue
}
var partition tmpPartition
err = cursor.One(&partition)
if err != nil {
_ = cursor.Close()
return err
}
err = cursor.Close()
if err != nil {
return err
}
new := old

var (
af metal.AddressFamily
defaultChildPrefixLength = metal.ChildPrefixLength{}
)
// FIXME check all prefixes
parsed, err := netip.ParsePrefix(new.Prefixes[0].String())
if err != nil {
return err
}
if parsed.Addr().Is4() {
af = metal.IPv4AddressFamily
defaultChildPrefixLength[af] = 22
}
if parsed.Addr().Is6() {
af = metal.IPv6AddressFamily
defaultChildPrefixLength[af] = 64
}

if new.AddressFamilies == nil {
new.AddressFamilies = metal.AddressFamilies{}
}
new.AddressFamilies = metal.AddressFamilies{af}

if new.PrivateSuper {
if new.DefaultChildPrefixLength == nil {
new.DefaultChildPrefixLength = make(map[metal.AddressFamily]uint8)
}
if partition.PrivateNetworkPrefixLength > 0 {
new.DefaultChildPrefixLength[af] = partition.PrivateNetworkPrefixLength
} else {
new.DefaultChildPrefixLength = defaultChildPrefixLength
}

}
err = rs.UpdateNetwork(&old, &new)
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 @@ -31,7 +31,7 @@ func Test_Migration(t *testing.T) {
_ = container.Terminate(context.Background())
}()

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

rs := datastore.New(log, c.IP+":"+c.Port, c.DB, c.User, c.Password)
rs.VRFPoolRangeMin = 10000
Expand Down Expand Up @@ -191,3 +191,134 @@ 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,
}
p3 = &tmpPartition{
ID: "p3",
}
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,
}
n4 = &metal.Network{
Base: metal.Base{
ID: "n4",
},
Prefixes: metal.Prefixes{
{IP: "100.1.0.0", Length: "22"},
},
PartitionID: "p3",
PrivateSuper: true,
}
)
_, 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 = r.DB("metal").Table("partition").Insert(p3).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.CreateNetwork(n4)
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[metal.IPv4AddressFamily], "childprefixlength:%v", n1fetched.DefaultChildPrefixLength)
require.Contains(t, n1fetched.AddressFamilies, metal.IPv4AddressFamily)

n2fetched, err := rs.FindNetworkByID(n2.ID)
require.NoError(t, err)
require.NotNil(t, n2fetched)
require.Equal(t, p2.PrivateNetworkPrefixLength, n2fetched.DefaultChildPrefixLength[metal.IPv6AddressFamily], "childprefixlength:%v", n2fetched.DefaultChildPrefixLength)
require.Contains(t, n2fetched.AddressFamilies, metal.IPv6AddressFamily)

n3fetched, err := rs.FindNetworkByID(n3.ID)
require.NoError(t, err)
require.NotNil(t, n3fetched)
require.Nil(t, n3fetched.DefaultChildPrefixLength)
require.Contains(t, n3fetched.AddressFamilies, metal.IPv4AddressFamily)

n4fetched, err := rs.FindNetworkByID(n4.ID)
require.NoError(t, err)
require.NotNil(t, n4fetched)
require.NotNil(t, n4fetched.DefaultChildPrefixLength)
require.Contains(t, n4fetched.AddressFamilies, metal.IPv4AddressFamily)
require.Equal(t, uint8(22), n4fetched.DefaultChildPrefixLength[metal.IPv4AddressFamily])
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func (_ *networkTestable) defaultBody(n *metal.Network) *metal.Network {
if n.AdditionalAnnouncableCIDRs == nil {
n.AdditionalAnnouncableCIDRs = []string{}
}
if n.AddressFamilies == nil {
n.AddressFamilies = metal.AddressFamilies{}
}
return n
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/metal-api/internal/datastore/rethinkdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,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
34 changes: 27 additions & 7 deletions cmd/metal-api/internal/ipam/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"net/netip"

"github.com/metal-stack/metal-api/cmd/metal-api/internal/metal"
"github.com/metal-stack/metal-lib/pkg/healthstatus"
Expand Down Expand Up @@ -51,10 +52,10 @@ func (i *ipam) AllocateChildPrefix(ctx context.Context, parentPrefix metal.Prefi
Length: uint32(childLength),
}))
if err != nil {
return nil, fmt.Errorf("error creating new prefix in ipam: %w", err)
return nil, fmt.Errorf("error creating new prefix from:%s in ipam: %w", parentPrefix.String(), err)
}

prefix, err := metal.NewPrefixFromCIDR(ipamPrefix.Msg.Prefix.Cidr)
prefix, _, err := metal.NewPrefixFromCIDR(ipamPrefix.Msg.Prefix.Cidr)
if err != nil {
return nil, fmt.Errorf("error creating prefix from ipam prefix: %w", err)
}
Expand Down Expand Up @@ -154,14 +155,33 @@ func (i *ipam) PrefixUsage(ctx context.Context, cidr string) (*metal.NetworkUsag
if err != nil {
return nil, fmt.Errorf("prefix usage for cidr:%s not found %w", cidr, err)
}

pfx, err := netip.ParsePrefix(cidr)
if err != nil {
return nil, err
}
af := metal.IPv4AddressFamily
if pfx.Addr().Is6() {
af = metal.IPv6AddressFamily
}
availableIPs := metal.AddressFamilyUsage{
af: usage.Msg.AvailableIps,
}
usedIPs := metal.AddressFamilyUsage{
af: usage.Msg.AcquiredIps,
}
availablePrefixes := metal.AddressFamilyUsage{
af: usage.Msg.AvailableSmallestPrefixes,
}
usedPrefixes := metal.AddressFamilyUsage{
af: usage.Msg.AcquiredPrefixes,
}
return &metal.NetworkUsage{
AvailableIPs: usage.Msg.AvailableIps,
UsedIPs: usage.Msg.AcquiredIps,
AvailableIPs: availableIPs,
UsedIPs: usedIPs,
// FIXME add usage.AvailablePrefixList as already done here
// https://github.com/metal-stack/metal-api/pull/152/files#diff-fe05f7f1480be933b5c482b74af28c8b9ca7ef2591f8341eb6e6663cbaeda7baR828
AvailablePrefixes: usage.Msg.AvailableSmallestPrefixes,
UsedPrefixes: usage.Msg.AcquiredPrefixes,
AvailablePrefixes: availablePrefixes,
UsedPrefixes: usedPrefixes,
}, nil
}

Expand Down
30 changes: 15 additions & 15 deletions cmd/metal-api/internal/metal/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,18 +215,18 @@ func (r EgressRule) Validate() error {
case ProtocolTCP, ProtocolUDP:
// ok
default:
return fmt.Errorf("invalid protocol: %s", r.Protocol)
return fmt.Errorf("egress rule has invalid protocol: %s", r.Protocol)
}

if err := validateComment(r.Comment); err != nil {
return err
return fmt.Errorf("egress rule with error:%w", err)
}
if err := validatePorts(r.Ports); err != nil {
return err
return fmt.Errorf("egress rule with error:%w", err)
}

if err := validateCIDRs(r.To); err != nil {
return err
return fmt.Errorf("egress rule with error:%w", err)
}

return nil
Expand All @@ -237,23 +237,23 @@ func (r IngressRule) Validate() error {
case ProtocolTCP, ProtocolUDP:
// ok
default:
return fmt.Errorf("invalid protocol: %s", r.Protocol)
return fmt.Errorf("ingress rule has invalid protocol: %s", r.Protocol)
}
if err := validateComment(r.Comment); err != nil {
return err
return fmt.Errorf("ingress rule with error:%w", err)
}

if err := validatePorts(r.Ports); err != nil {
return err
return fmt.Errorf("ingress rule with error:%w", err)
}
if err := validateCIDRs(r.To); err != nil {
return err
return fmt.Errorf("ingress rule with error:%w", err)
}
if err := validateCIDRs(r.From); err != nil {
return err
return fmt.Errorf("ingress rule with error:%w", err)
}
if err := validateCIDRs(slices.Concat(r.From, r.To)); err != nil {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this was ok to delete

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is required for ingress rules where to and from can be specified

return err
return fmt.Errorf("ingress rule with error:%w", err)
}

return nil
Expand Down Expand Up @@ -287,17 +287,17 @@ func validatePorts(ports []int) error {
}

func validateCIDRs(cidrs []string) error {
af := ""
var af AddressFamily
for _, cidr := range cidrs {
p, err := netip.ParsePrefix(cidr)
if err != nil {
return fmt.Errorf("invalid cidr: %w", err)
}
var newaf string
var newaf AddressFamily
if p.Addr().Is4() {
newaf = "ipv4"
} else {
newaf = "ipv6"
newaf = IPv4AddressFamily
} else if p.Addr().Is6() {
newaf = IPv6AddressFamily
}
if af != "" && af != newaf {
return fmt.Errorf("mixed address family in one rule is not supported:%v", cidrs)
Expand Down
Loading
Loading