Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/metal-stack/metal-api int…
Browse files Browse the repository at this point in the history
…o dualstack-support
  • Loading branch information
majst01 committed Jul 25, 2024
2 parents 1b09d5d + d5e5fcf commit d49d2c0
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 128 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package migrations

import (
"net/netip"

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

"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore"
Expand Down Expand Up @@ -34,21 +36,28 @@ func init() {
// TODO: does not work somehow
new := old

af, err := metal.GetAddressFamily(new.Prefixes)
var af metal.AddressFamily
parsed, err := netip.ParsePrefix(new.Prefixes[0].String())
if err != nil {
return err
}
if af != nil {
if new.AddressFamilies == nil {
new.AddressFamilies = make(map[metal.AddressFamily]bool)
}
new.AddressFamilies[*af] = true
if parsed.Addr().Is4() {
af = metal.IPv4AddressFamily
}
if parsed.Addr().Is6() {
af = metal.IPv6AddressFamily
}

if new.AddressFamilies == nil {
new.AddressFamilies = make(map[metal.AddressFamily]bool)
}
new.AddressFamilies[af] = true

if new.PrivateSuper {
if new.DefaultChildPrefixLength == nil {
new.DefaultChildPrefixLength = make(map[metal.AddressFamily]uint8)
}
new.DefaultChildPrefixLength[*af] = partition.PrivateNetworkPrefixLength
new.DefaultChildPrefixLength[af] = partition.PrivateNetworkPrefixLength
}
err = rs.UpdateNetwork(&old, &new)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/metal-api/internal/ipam/ipam.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ 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)
Expand Down
20 changes: 0 additions & 20 deletions cmd/metal-api/internal/metal/network.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package metal

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

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

Expand Down Expand Up @@ -351,21 +349,3 @@ 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)
}
46 changes: 0 additions & 46 deletions cmd/metal-api/internal/metal/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import (
"fmt"
"reflect"
"testing"

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

func TestNics_ByIdentifier(t *testing.T) {
Expand Down Expand Up @@ -337,47 +335,3 @@ func TestNicState_SetState(t *testing.T) {
})
}
}

func Test_getAddressFamily(t *testing.T) {
tests := []struct {
name string
prefixes Prefixes
want *AddressFamily
wantErr bool
}{
{
name: "ipv4",
prefixes: Prefixes{{IP: "10.0.0.0", Length: "8"}},
want: pointer.Pointer(IPv4AddressFamily),
},
{
name: "ipv6",
prefixes: Prefixes{{IP: "2001::", Length: "64"}},
want: pointer.Pointer(IPv6AddressFamily),
},
{
name: "empty prefixes",
prefixes: Prefixes{},
want: nil,
wantErr: false,
},
{
name: "malformed ipv4",
prefixes: Prefixes{{IP: "10.0.0.0.0", Length: "6"}},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetAddressFamily(tt.prefixes)
if (err != nil) != tt.wantErr {
t.Errorf("getAddressFamily() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getAddressFamily() = %v, want %v", got, tt.want)
}
})
}
}
11 changes: 7 additions & 4 deletions cmd/metal-api/internal/service/async-actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import (
"fmt"
"log/slog"

"connectrpc.com/connect"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/headscale"

ipamer "github.com/metal-stack/go-ipam"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/ipam"
"github.com/metal-stack/metal-api/cmd/metal-api/internal/metal"
Expand Down Expand Up @@ -216,12 +216,15 @@ func (a *asyncActor) releaseIP(ip metal.IP) error {

// now the IP should not exist any more in our datastore
// so cleanup the ipam

ctx := context.Background()
err = a.ReleaseIP(ctx, ip)
if err != nil {
if errors.Is(err, ipamer.ErrNotFound) {
return nil
var connectErr *connect.Error
if errors.As(err, &connectErr) {
if connectErr.Code() == connect.CodeNotFound {
return nil
}
}
return fmt.Errorf("cannot release IP %q: %w", ip, err)
}
Expand Down
11 changes: 6 additions & 5 deletions cmd/metal-api/internal/service/ip-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ import (

v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1"

goipam "github.com/metal-stack/go-ipam"

restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/metal-stack/metal-lib/httperrors"
Expand Down Expand Up @@ -471,10 +469,13 @@ func allocateRandomIP(ctx context.Context, parent *metal.Network, ipamer ipam.IP
}

ipAddress, err = ipamer.AllocateIP(ctx, prefix)
if err != nil && errors.Is(err, goipam.ErrNoIPAvailable) {
continue
}
if err != nil {
var connectErr *connect.Error
if errors.As(err, &connectErr) {
if connectErr.Code() == connect.CodeNotFound {
continue
}
}
return "", "", err
}

Expand Down
50 changes: 34 additions & 16 deletions cmd/metal-api/internal/service/network-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"net/netip"

"connectrpc.com/connect"
mdmv1 "github.com/metal-stack/masterdata-api/api/v1"
mdm "github.com/metal-stack/masterdata-api/pkg/client"

Expand Down Expand Up @@ -605,7 +606,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re

// Allow configurable prefix length per AF
length := superNetwork.DefaultChildPrefixLength
if requestPayload.Length != nil {
if len(requestPayload.Length) > 0 {
for af, l := range requestPayload.Length {
length[metal.ToAddressFamily(string(af))] = l
}
Expand All @@ -626,7 +627,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re
r.log.Info("network allocate", "supernetwork", superNetwork.ID, "defaultchildprefixlength", superNetwork.DefaultChildPrefixLength, "length", length)

ctx := request.Request.Context()
nw, err := createChildNetwork(ctx, r.ds, r.ipamer, nwSpec, &superNetwork, length)
nw, err := r.createChildNetwork(ctx, nwSpec, &superNetwork, length)
if err != nil {
r.sendError(request, response, defaultError(err))
return
Expand All @@ -641,20 +642,20 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re
r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage))
}

func createChildNetwork(ctx context.Context, ds *datastore.RethinkStore, ipamer ipam.IPAMer, nwSpec *metal.Network, parent *metal.Network, childLengths metal.ChildPrefixLength) (*metal.Network, error) {
vrf, err := acquireRandomVRF(ds)
func (r *networkResource) createChildNetwork(ctx context.Context, nwSpec *metal.Network, parent *metal.Network, childLengths metal.ChildPrefixLength) (*metal.Network, error) {
vrf, err := acquireRandomVRF(r.ds)
if err != nil {
return nil, fmt.Errorf("could not acquire a vrf: %w", err)
}

var childPrefixes = metal.Prefixes{}
for af, childLength := range childLengths {
childPrefix, err := createChildPrefix(ctx, parent.Prefixes, childLength, ipamer)
childPrefix, err := r.createChildPrefix(ctx, parent.Prefixes, af, childLength)
if err != nil {
return nil, err
}
if childPrefix == nil {
return nil, fmt.Errorf("could not allocate child prefix in parent network: %s for addressfamily: %s", parent.ID, af)
return nil, fmt.Errorf("could not allocate child prefix in parent network: %s for addressfamily: %s length:%d", parent.ID, af, childLength)
}
childPrefixes = append(childPrefixes, *childPrefix)
}
Expand All @@ -678,7 +679,7 @@ func createChildNetwork(ctx context.Context, ds *datastore.RethinkStore, ipamer
AddressFamilies: nwSpec.AddressFamilies,
}

err = ds.CreateNetwork(nw)
err = r.ds.CreateNetwork(nw)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -925,25 +926,42 @@ func getNetworkUsage(ctx context.Context, nw *metal.Network, ipamer ipam.IPAMer)
return usage, nil
}

func createChildPrefix(ctx context.Context, parentPrefixes metal.Prefixes, childLength uint8, ipamer ipam.IPAMer) (*metal.Prefix, error) {
var errors []error
var err error
var childPrefix *metal.Prefix
func (r *networkResource) createChildPrefix(ctx context.Context, parentPrefixes metal.Prefixes, af metal.AddressFamily, childLength uint8) (*metal.Prefix, error) {
var (
errs []error
childPrefix *metal.Prefix
)
for _, parentPrefix := range parentPrefixes {
childPrefix, err = ipamer.AllocateChildPrefix(ctx, parentPrefix, childLength)
pfx, err := netip.ParsePrefix(parentPrefix.String())
if err != nil {
errors = append(errors, err)
return nil, fmt.Errorf("unable to parse prefix: %w", err)
}
if pfx.Addr().Is4() && af == metal.IPv6AddressFamily {
continue
}
if pfx.Addr().Is6() && af == metal.IPv4AddressFamily {
continue
}
childPrefix, err = r.ipamer.AllocateChildPrefix(ctx, parentPrefix, childLength)
if err != nil {
var connectErr *connect.Error
if errors.As(err, &connectErr) {
if connectErr.Code() == connect.CodeNotFound {
continue
}
}
errs = append(errs, err)
continue
}
if childPrefix != nil {
break
}
}
if childPrefix == nil {
if len(errors) > 0 {
return nil, fmt.Errorf("cannot allocate free child prefix in ipam: %v", errors)
if len(errs) > 0 {
return nil, fmt.Errorf("cannot allocate free child prefix in ipam: %w", errors.Join(errs...))
}
return nil, fmt.Errorf("cannot allocate free child prefix in one of the given parent prefixes in ipam: %v", parentPrefixes)
return nil, fmt.Errorf("cannot allocate free child prefix in one of the given parent prefixes in ipam: %s", parentPrefixes.String())
}

return childPrefix, nil
Expand Down
40 changes: 29 additions & 11 deletions cmd/metal-api/internal/service/v1/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ type NetworkImmutable struct {

// NetworkUsage reports core metrics about available and used IPs or Prefixes in a Network.
type NetworkUsage struct {
AvailableIPs map[metal.AddressFamily]uint64 `json:"available_ips" description:"the total available IPs" readonly:"true"`
UsedIPs map[metal.AddressFamily]uint64 `json:"used_ips" description:"the total used IPs" readonly:"true"`
AvailablePrefixes map[metal.AddressFamily]uint64 `json:"available_prefixes" description:"the total available 2 bit Prefixes" readonly:"true"`
UsedPrefixes map[metal.AddressFamily]uint64 `json:"used_prefixes" description:"the total used Prefixes" readonly:"true"`
AvailableIPs uint64 `json:"available_ips" description:"the total available IPs" readonly:"true"`
UsedIPs uint64 `json:"used_ips" description:"the total used IPs" readonly:"true"`
AvailablePrefixes uint64 `json:"available_prefixes" description:"the total available 2 bit Prefixes" readonly:"true"`
UsedPrefixes uint64 `json:"used_prefixes" description:"the total used Prefixes" readonly:"true"`
}

// NetworkCreateRequest is used to create a new Network.
Expand Down Expand Up @@ -74,7 +74,8 @@ type NetworkResponse struct {
Common
NetworkBase
NetworkImmutable
Usage NetworkUsage `json:"usage" description:"usage of ips and prefixes in this network" readonly:"true"`
Usage NetworkUsage `json:"usage" description:"usage of IPv4 ips and prefixes in this network" readonly:"true"`
UsageV6 NetworkUsage `json:"usagev6" description:"usage of IPv6 ips and prefixes in this network" readonly:"true"`
Timestamps
}

Expand All @@ -86,6 +87,8 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw

var (
parentNetworkID *string
usagev4 NetworkUsage
usagev6 NetworkUsage
)

if network.ParentNetworkID != "" {
Expand All @@ -96,6 +99,25 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw
labels = make(map[string]string)
}

for af := range network.AddressFamilies {
if af == metal.IPv4AddressFamily {
usagev4 = NetworkUsage{
AvailableIPs: usage.AvailableIPs[af],
UsedIPs: usage.UsedIPs[af],
AvailablePrefixes: usage.AvailablePrefixes[af],
UsedPrefixes: usage.UsedPrefixes[af],
}
}
if af == metal.IPv6AddressFamily {
usagev6 = NetworkUsage{
AvailableIPs: usage.AvailableIPs[af],
UsedIPs: usage.UsedIPs[af],
AvailablePrefixes: usage.AvailablePrefixes[af],
UsedPrefixes: usage.UsedPrefixes[af],
}
}
}

return &NetworkResponse{
Common: Common{
Identifiable: Identifiable{
Expand Down Expand Up @@ -123,12 +145,8 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw
ParentNetworkID: parentNetworkID,
AddressFamilies: network.AddressFamilies,
},
Usage: NetworkUsage{
AvailableIPs: usage.AvailableIPs,
UsedIPs: usage.UsedIPs,
AvailablePrefixes: usage.AvailablePrefixes,
UsedPrefixes: usage.UsedPrefixes,
},
Usage: usagev4,
UsageV6: usagev6,
Timestamps: Timestamps{
Created: network.Created,
Changed: network.Changed,
Expand Down
Loading

0 comments on commit d49d2c0

Please sign in to comment.