diff --git a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go index f7d56961..940c5cbd 100644 --- a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go +++ b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go @@ -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" @@ -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 { diff --git a/cmd/metal-api/internal/ipam/ipam.go b/cmd/metal-api/internal/ipam/ipam.go index f820187b..326dfc92 100644 --- a/cmd/metal-api/internal/ipam/ipam.go +++ b/cmd/metal-api/internal/ipam/ipam.go @@ -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) diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index f1b2e53b..eaff83bc 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -1,12 +1,10 @@ package metal import ( - "fmt" "net" "net/netip" "strconv" - "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/samber/lo" ) @@ -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) -} diff --git a/cmd/metal-api/internal/metal/network_test.go b/cmd/metal-api/internal/metal/network_test.go index a62ad927..a7dcfddf 100644 --- a/cmd/metal-api/internal/metal/network_test.go +++ b/cmd/metal-api/internal/metal/network_test.go @@ -4,8 +4,6 @@ import ( "fmt" "reflect" "testing" - - "github.com/metal-stack/metal-lib/pkg/pointer" ) func TestNics_ByIdentifier(t *testing.T) { @@ -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) - } - }) - } -} diff --git a/cmd/metal-api/internal/service/async-actor.go b/cmd/metal-api/internal/service/async-actor.go index b4284f1b..507f509e 100644 --- a/cmd/metal-api/internal/service/async-actor.go +++ b/cmd/metal-api/internal/service/async-actor.go @@ -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" @@ -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) } diff --git a/cmd/metal-api/internal/service/ip-service.go b/cmd/metal-api/internal/service/ip-service.go index a0891385..0f65ae61 100644 --- a/cmd/metal-api/internal/service/ip-service.go +++ b/cmd/metal-api/internal/service/ip-service.go @@ -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" @@ -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 } diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index 49e95bd4..5dfbaa21 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -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" @@ -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 } @@ -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 @@ -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) } @@ -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 } @@ -925,14 +926,31 @@ 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 { @@ -940,10 +958,10 @@ func createChildPrefix(ctx context.Context, parentPrefixes metal.Prefixes, child } } 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 diff --git a/cmd/metal-api/internal/service/v1/network.go b/cmd/metal-api/internal/service/v1/network.go index fd854531..b037db12 100644 --- a/cmd/metal-api/internal/service/v1/network.go +++ b/cmd/metal-api/internal/service/v1/network.go @@ -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. @@ -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 } @@ -86,6 +87,8 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw var ( parentNetworkID *string + usagev4 NetworkUsage + usagev6 NetworkUsage ) if network.ParentNetworkID != "" { @@ -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{ @@ -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, diff --git a/go.mod b/go.mod index d8e197ca..b9d13f77 100644 --- a/go.mod +++ b/go.mod @@ -70,6 +70,7 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/containerd v1.7.19 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-oidc/v3 v3.10.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect @@ -151,6 +152,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.7.1 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect diff --git a/spec/metal-api.json b/spec/metal-api.json index 81141fd9..ce13495f 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -3968,7 +3968,11 @@ }, "usage": { "$ref": "#/definitions/v1.NetworkUsage", - "description": "usage of ips and prefixes in this network" + "description": "usage of IPv4 ips and prefixes in this network" + }, + "usagev6": { + "$ref": "#/definitions/v1.NetworkUsage", + "description": "usage of IPv6 ips and prefixes in this network" }, "vrf": { "description": "the vrf this network is associated with", @@ -3988,7 +3992,8 @@ "prefixes", "privatesuper", "underlay", - "usage" + "usage", + "usagev6" ] }, "v1.NetworkUpdateRequest": { @@ -4038,32 +4043,24 @@ "v1.NetworkUsage": { "properties": { "available_ips": { - "additionalProperties": { - "type": "integer" - }, "description": "the total available IPs", - "type": "object" + "format": "integer", + "type": "integer" }, "available_prefixes": { - "additionalProperties": { - "type": "integer" - }, "description": "the total available 2 bit Prefixes", - "type": "object" + "format": "integer", + "type": "integer" }, "used_ips": { - "additionalProperties": { - "type": "integer" - }, "description": "the total used IPs", - "type": "object" + "format": "integer", + "type": "integer" }, "used_prefixes": { - "additionalProperties": { - "type": "integer" - }, "description": "the total used Prefixes", - "type": "object" + "format": "integer", + "type": "integer" } }, "required": [