From 5ea7ea6e1385fc8fc071368b698438cfdc60c039 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 4 Jul 2024 08:39:39 +0200 Subject: [PATCH 01/12] IPv6 Support --- .../migrations/06_childprefixlength.go | 53 ++++ cmd/metal-api/internal/metal/network.go | 1 + cmd/metal-api/internal/metal/partition.go | 7 +- .../internal/service/integration_test.go | 6 +- .../internal/service/ip-service_test.go | 29 +- .../internal/service/network-service.go | 115 +++++++- .../internal/service/network-service_test.go | 264 ++++++++++++++++++ .../internal/service/partition-service.go | 14 +- cmd/metal-api/internal/service/v1/network.go | 25 ++ .../internal/service/v1/partition.go | 8 +- cmd/metal-api/internal/testdata/testdata.go | 27 +- spec/metal-api.json | 45 +-- 12 files changed, 532 insertions(+), 62 deletions(-) create mode 100644 cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go diff --git a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go new file mode 100644 index 000000000..11526c805 --- /dev/null +++ b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go @@ -0,0 +1,53 @@ +package migrations + +import ( + r "gopkg.in/rethinkdb/rethinkdb-go.v6" + + "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" +) + +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 { + if !old.PrivateSuper { + continue + } + + 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 + } + + new := old + new.ChildPrefixLength = &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 + }, + }) +} diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 9f2477d71..be55ee977 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -209,6 +209,7 @@ type Network struct { Base Prefixes Prefixes `rethinkdb:"prefixes" json:"prefixes"` DestinationPrefixes Prefixes `rethinkdb:"destinationprefixes" json:"destinationprefixes"` + ChildPrefixLength *uint8 `rethinkdb:"childprefixlength" 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"` diff --git a/cmd/metal-api/internal/metal/partition.go b/cmd/metal-api/internal/metal/partition.go index 2db7c2f8a..09fb8795d 100644 --- a/cmd/metal-api/internal/metal/partition.go +++ b/cmd/metal-api/internal/metal/partition.go @@ -3,10 +3,9 @@ package metal // A Partition represents a location. type Partition struct { Base - BootConfiguration BootConfiguration `rethinkdb:"bootconfig" json:"bootconfig"` - MgmtServiceAddress string `rethinkdb:"mgmtserviceaddr" json:"mgmtserviceaddr"` - PrivateNetworkPrefixLength uint8 `rethinkdb:"privatenetworkprefixlength" json:"privatenetworkprefixlength"` - Labels map[string]string `rethinkdb:"labels" json:"labels"` + BootConfiguration BootConfiguration `rethinkdb:"bootconfig" json:"bootconfig"` + MgmtServiceAddress string `rethinkdb:"mgmtserviceaddr" json:"mgmtserviceaddr"` + Labels map[string]string `rethinkdb:"labels" json:"labels"` } // BootConfiguration defines the metal-hammer initrd, kernel and commandline diff --git a/cmd/metal-api/internal/service/integration_test.go b/cmd/metal-api/internal/service/integration_test.go index 10a1f16c8..eb10d04d5 100644 --- a/cmd/metal-api/internal/service/integration_test.go +++ b/cmd/metal-api/internal/service/integration_test.go @@ -23,6 +23,7 @@ import ( metalgrpc "github.com/metal-stack/metal-api/cmd/metal-api/internal/grpc" "github.com/metal-stack/metal-api/test" "github.com/metal-stack/metal-lib/bus" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/security" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" @@ -294,8 +295,9 @@ func createTestEnvironment(t *testing.T) testEnv { PartitionID: &partition.ID, }, NetworkImmutable: v1.NetworkImmutable{ - Prefixes: []string{testPrivateSuperCidr}, - PrivateSuper: true, + Prefixes: []string{testPrivateSuperCidr}, + PrivateSuper: true, + ChildPrefixLength: pointer.Pointer(uint8(22)), }, } status = te.networkCreate(t, ncr, &createdNetwork) diff --git a/cmd/metal-api/internal/service/ip-service_test.go b/cmd/metal-api/internal/service/ip-service_test.go index c473ba5f9..72889fd5a 100644 --- a/cmd/metal-api/internal/service/ip-service_test.go +++ b/cmd/metal-api/internal/service/ip-service_test.go @@ -52,13 +52,15 @@ func TestGetIPs(t *testing.T) { err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) - require.Len(t, result, 3) + require.Len(t, result, 4) require.Equal(t, testdata.IP1.IPAddress, result[0].IPAddress) require.Equal(t, testdata.IP1.Name, *result[0].Name) require.Equal(t, testdata.IP2.IPAddress, result[1].IPAddress) require.Equal(t, testdata.IP2.Name, *result[1].Name) require.Equal(t, testdata.IP3.IPAddress, result[2].IPAddress) require.Equal(t, testdata.IP3.Name, *result[2].Name) + require.Equal(t, testdata.IP4.IPAddress, result[3].IPAddress) + require.Equal(t, testdata.IP4.Name, *result[3].Name) } func TestGetIP(t *testing.T) { @@ -85,6 +87,31 @@ func TestGetIP(t *testing.T) { require.Equal(t, testdata.IP1.Name, *result.Name) } +func TestGetIPv6(t *testing.T) { + ds, mock := datastore.InitMockDB(t) + testdata.InitMockDBData(mock) + + logger := slog.Default() + ipservice, err := NewIP(logger, ds, bus.DirectEndpoints(), ipam.InitTestIpam(t), nil) + require.NoError(t, err) + container := restful.NewContainer().Add(ipservice) + req := httptest.NewRequest("GET", "/v1/ip/2001:0db8:85a3::1", nil) + container = injectViewer(logger, container, req) + w := httptest.NewRecorder() + container.ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + + require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String()) + var result v1.IPResponse + err = json.NewDecoder(resp.Body).Decode(&result) + + require.NoError(t, err) + require.Equal(t, testdata.IP4.IPAddress, result.IPAddress) + require.Equal(t, testdata.IP4.Name, *result.Name) +} + func TestGetIPNotFound(t *testing.T) { ds, mock := datastore.InitMockDB(t) testdata.InitMockDBData(mock) diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index bd4db1d64..a1a31357e 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/http" + "net/netip" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" mdm "github.com/metal-stack/masterdata-api/pkg/client" @@ -205,6 +206,7 @@ func (r *networkResource) findNetworks(request *restful.Request, response *restf r.send(request, response, http.StatusOK, result) } +// TODO allow creation of networks with childprefixlength which are not privatesuper func (r *networkResource) createNetwork(request *restful.Request, response *restful.Response) { var requestPayload v1.NetworkCreateRequest err := request.ReadEntity(&requestPayload) @@ -260,7 +262,8 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest } prefixes := metal.Prefixes{} - // all Prefixes must be valid + addressFamilies := make(map[string]bool) + // all Prefixes must be valid and from the same addressfamily for i := range requestPayload.Prefixes { p := requestPayload.Prefixes[i] prefix, err := metal.NewPrefixFromCIDR(p) @@ -268,10 +271,25 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) return } - + ipprefix, err := netip.ParsePrefix(p) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) + return + } + if ipprefix.Addr().Is4() { + addressFamilies["ipv4"] = true + } + if ipprefix.Addr().Is6() { + addressFamilies["ipv6"] = true + } prefixes = append(prefixes, *prefix) } + if len(addressFamilies) > 1 { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefixes have different addressfamilies"))) + return + } + destPrefixes := metal.Prefixes{} for i := range requestPayload.DestinationPrefixes { p := requestPayload.DestinationPrefixes[i] @@ -324,17 +342,38 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } + // We should support two private super per partition, one per addressfamily + // the network allocate request must be configurable with addressfamily if privateSuper { boolTrue := true - err := r.ds.FindNetwork(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &metal.Network{}) + var nw metal.Network + err := r.ds.FindNetwork(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &nw) if err != nil { if !metal.IsNotFound(err) { r.sendError(request, response, defaultError(err)) return } } else { - r.sendError(request, response, defaultError(fmt.Errorf("partition with id %q already has a private super network", partition.ID))) - return + if len(nw.Prefixes) != 0 { + existingsuper := nw.Prefixes[0].String() + + ipprefxexistingsuper, err := netip.ParsePrefix(existingsuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not valid: %w", existingsuper, err))) + return + + } + newsuper := prefixes[0].String() + ipprefixnew, err := netip.ParsePrefix(newsuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not valid: %w", newsuper, err))) + return + } + if (ipprefixnew.Addr().Is4() && ipprefxexistingsuper.Addr().Is4()) || (ipprefixnew.Addr().Is6() && ipprefxexistingsuper.Addr().Is6()) { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("partition with id %q already has a private super network for this addressfamily", partition.ID))) + return + } + } } } if underlay { @@ -389,6 +428,23 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest Labels: labels, } + // check if childprefixlength is set and matches addressfamily + if requestPayload.ChildPrefixLength != nil && privateSuper { + cpl := *requestPayload.ChildPrefixLength + for _, p := range prefixes { + ipprefix, err := netip.ParsePrefix(p.String()) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) + return + } + if cpl <= uint8(ipprefix.Bits()) { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given childprefixlength %d is not greater than prefix length of:%s", cpl, p.String()))) + return + } + } + nw.ChildPrefixLength = requestPayload.ChildPrefixLength + } + ctx := request.Request.Context() for _, p := range nw.Prefixes { err := r.ipamer.CreatePrefix(ctx, p) @@ -409,6 +465,7 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage)) } +// TODO add possibility to allocate from a non super network if given in the AllocateRequest and super has childprefixlength func (r *networkResource) allocateNetwork(request *restful.Request, response *restful.Response) { var requestPayload v1.NetworkAllocateRequest err := request.ReadEntity(&requestPayload) @@ -463,9 +520,9 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re return } - var superNetwork metal.Network + var superNetworks metal.Networks boolTrue := true - err = r.ds.FindNetwork(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &superNetwork) + err = r.ds.SearchNetworks(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &superNetworks) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -482,6 +539,37 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re destPrefixes = append(destPrefixes, *prefix) } + addressFamily := v1.IPv4AddressFamily + if requestPayload.AddressFamily != nil { + addressFamily = v1.ToAddressFamily(*requestPayload.AddressFamily) + } + + r.log.Info("network allocate", "family", addressFamily) + var ( + superNetwork metal.Network + superNetworkFound bool + ) + for _, snw := range superNetworks { + ipprefix, err := netip.ParsePrefix(snw.Prefixes[0].String()) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + if addressFamily == v1.IPv4AddressFamily && ipprefix.Addr().Is4() { + superNetwork = snw + superNetworkFound = true + } + if addressFamily == v1.IPv6AddressFamily && ipprefix.Addr().Is6() { + superNetwork = snw + superNetworkFound = true + } + } + if !superNetworkFound { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("no supernetwork for addressfamily:%s found", addressFamily))) + return + } + r.log.Info("network allocate", "supernetwork", superNetwork.ID) + nwSpec := &metal.Network{ Base: metal.Base{ Name: name, @@ -494,8 +582,19 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re Shared: shared, Nat: nat, } + if superNetwork.ChildPrefixLength == nil { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("supernetwork %s has no childprefixlength specified", superNetwork.ID))) + return + } + + // Allow configurable prefix length + length := *superNetwork.ChildPrefixLength + if requestPayload.Length != nil { + length = *requestPayload.Length + } + ctx := request.Request.Context() - nw, err := createChildNetwork(ctx, r.ds, r.ipamer, nwSpec, &superNetwork, partition.PrivateNetworkPrefixLength) + nw, err := createChildNetwork(ctx, r.ds, r.ipamer, nwSpec, &superNetwork, length) if err != nil { r.sendError(request, response, defaultError(err)) return diff --git a/cmd/metal-api/internal/service/network-service_test.go b/cmd/metal-api/internal/service/network-service_test.go index 4e54993a9..c57ee945d 100644 --- a/cmd/metal-api/internal/service/network-service_test.go +++ b/cmd/metal-api/internal/service/network-service_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/metal-stack/metal-lib/httperrors" + "github.com/metal-stack/metal-lib/pkg/pointer" r "gopkg.in/rethinkdb/rethinkdb-go.v6" "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" @@ -252,3 +253,266 @@ func TestSearchNetwork(t *testing.T) { require.Equal(t, testdata.Nw1.PartitionID, *result.PartitionID) require.Equal(t, testdata.Nw1.Name, *result.Name) } + +func Test_networkResource_createNetwork(t *testing.T) { + log := slog.Default() + tests := []struct { + name string + networkName string + networkID string + partitionID string + projectID string + prefixes []string + destinationPrefixes []string + vrf uint + childprefixlength *uint8 + privateSuper bool + underlay bool + nat bool + expectedStatus int + expectedErrorMessage string + }{ + { + name: "simple IPv4", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"172.0.0.0/24"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + vrf: uint(10000), + expectedStatus: http.StatusCreated, + }, + { + name: "privatesuper IPv4", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"172.0.0.0/24"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + privateSuper: true, + vrf: uint(10000), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "partition with id \"1\" already has a private super network for this addressfamily", + }, + { + name: "privatesuper IPv6", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"fdaa:bbcc::/50"}, + destinationPrefixes: []string{"::/0"}, + privateSuper: true, + vrf: uint(10000), + expectedStatus: http.StatusCreated, + }, + { + name: "broken IPv4", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"192.168.265.0/24"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + privateSuper: true, + vrf: uint(10000), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "given prefix 192.168.265.0/24 is not a valid ip with mask: netip.ParsePrefix(\"192.168.265.0/24\"): ParseAddr(\"192.168.265.0\"): IPv4 field has value >255", + }, + { + name: "broken IPv6", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"fdaa:::/50"}, + destinationPrefixes: []string{"::/0"}, + privateSuper: true, + vrf: uint(10000), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "given prefix fdaa:::/50 is not a valid ip with mask: netip.ParsePrefix(\"fdaa:::/50\"): ParseAddr(\"fdaa:::\"): each colon-separated field must have at least one digit (at \":\")", + }, + { + name: "mixed prefix addressfamilies", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"172.0.0.0/24", "fdaa:bbcc::/50"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + vrf: uint(10000), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "given prefixes have different addressfamilies", + }, + { + name: "broken destinationprefix", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"172.0.0.0/24"}, + destinationPrefixes: []string{"0.0.0.0/33"}, + vrf: uint(10000), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "given prefix 0.0.0.0/33 is not a valid ip with mask: netip.ParsePrefix(\"0.0.0.0/33\"): prefix length out of range", + }, + { + name: "broken childprefixlength", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"fdaa:bbcc::/50"}, + childprefixlength: pointer.Pointer(uint8(50)), + privateSuper: true, + vrf: uint(10000), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "given childprefixlength 50 is not greater than prefix length of:fdaa:bbcc::/50", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ds, mock := datastore.InitMockDB(t) + ipamer, err := testdata.InitMockIpamData(mock, false) + require.NoError(t, err) + testdata.InitMockDBData(mock) + + networkservice := NewNetwork(log, ds, ipamer, nil) + container := restful.NewContainer().Add(networkservice) + + createRequest := &v1.NetworkCreateRequest{ + Describable: v1.Describable{Name: &tt.networkName}, + NetworkBase: v1.NetworkBase{PartitionID: &tt.partitionID, ProjectID: &tt.projectID}, + NetworkImmutable: v1.NetworkImmutable{ + Prefixes: tt.prefixes, + DestinationPrefixes: tt.destinationPrefixes, + Vrf: &tt.vrf, Nat: tt.nat, PrivateSuper: tt.privateSuper, Underlay: tt.underlay, + }, + } + if tt.childprefixlength != nil { + createRequest.ChildPrefixLength = tt.childprefixlength + } + js, _ := json.Marshal(createRequest) + body := bytes.NewBuffer(js) + req := httptest.NewRequest("PUT", "/v1/network", body) + req.Header.Add("Content-Type", "application/json") + container = injectAdmin(log, container, req) + w := httptest.NewRecorder() + container.ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + + require.Equal(t, tt.expectedStatus, resp.StatusCode, w.Body.String()) + if tt.expectedStatus > 300 { + var result httperrors.HTTPErrorResponse + err := json.NewDecoder(resp.Body).Decode(&result) + + require.NoError(t, err) + require.Equal(t, tt.expectedErrorMessage, result.Message) + } else { + var result v1.NetworkResponse + err = json.NewDecoder(resp.Body).Decode(&result) + require.NoError(t, err) + require.Equal(t, tt.networkName, *result.Name) + require.Equal(t, tt.partitionID, *result.PartitionID) + require.Equal(t, tt.projectID, *result.ProjectID) + require.Equal(t, tt.destinationPrefixes, result.DestinationPrefixes) + if tt.childprefixlength != nil { + require.Equal(t, tt.childprefixlength, result.ChildPrefixLength) + } + } + }) + } +} + +// func Test_networkResource_allocateNetwork(t *testing.T) { +// log := slog.Default() +// tests := []struct { +// name string +// networkName string +// partitionID string +// projectID string +// childprefixlength *uint8 +// addressFamily *string +// shared bool +// expectedStatus int +// expectedErrorMessage string +// }{ +// { +// name: "simple ipv4", +// networkName: "tenantv4", +// partitionID: "1", +// projectID: "project-1", +// expectedStatus: http.StatusCreated, +// }, +// { +// name: "ipv6 without ipv6 super", +// networkName: "tenantv6", +// partitionID: "1", +// projectID: "project-1", +// addressFamily: pointer.Pointer("ipv6"), +// expectedStatus: http.StatusUnprocessableEntity, +// expectedErrorMessage: "no supernetwork for addressfamily:IPv6 found", +// }, +// } +// for _, tt := range tests { +// ds, mock := datastore.InitMockDB(t) + +// ipamer, err := testdata.InitMockIpamData(mock, false) +// require.Nil(t, err) +// mock.On(r.DB("mockdb").Table("network").Filter(r.MockAnything()).Filter(r.MockAnything())).Return(metal.Networks{testdata.Nw1, testdata.Nw2}, nil) +// changes := []r.ChangeResponse{{OldValue: map[string]interface{}{"id": float64(42)}}} +// mock.On(r.DB("mockdb").Table("integerpool").Limit(1).Delete(r. +// DeleteOpts{ReturnChanges: true})).Return(r.WriteResponse{Changes: changes}, nil) + +// mock.On(r.DB("mockdb").Table("partition").Get(r.MockAnything())).Return( +// metal.Partition{ +// Base: metal.Base{ID: tt.partitionID}, +// }, +// nil, +// ) +// testdata.InitMockDBData(mock) + +// psc := mdmock.ProjectServiceClient{} +// psc.On("Get", context.Background(), &mdmv1.ProjectGetRequest{Id: "project-1"}).Return(&mdmv1.ProjectResponse{ +// Project: &mdmv1.Project{ +// Meta: &mdmv1.Meta{Id: tt.projectID}, +// }, +// }, nil, +// ) +// tsc := mdmock.TenantServiceClient{} + +// mdc := mdm.NewMock(&psc, &tsc) + +// networkservice := NewNetwork(log, ds, ipamer, mdc) +// container := restful.NewContainer().Add(networkservice) + +// allocateRequest := &v1.NetworkAllocateRequest{ +// Describable: v1.Describable{Name: &tt.networkName}, +// NetworkBase: v1.NetworkBase{PartitionID: &tt.partitionID, ProjectID: &tt.projectID}, +// AddressFamily: tt.addressFamily, +// Length: tt.childprefixlength, +// } + +// js, _ := json.Marshal(allocateRequest) +// body := bytes.NewBuffer(js) +// req := httptest.NewRequest("POST", "/v1/network/allocate", body) +// req.Header.Add("Content-Type", "application/json") +// container = injectAdmin(log, container, req) +// w := httptest.NewRecorder() +// container.ServeHTTP(w, req) + +// resp := w.Result() +// require.Equal(t, tt.expectedStatus, resp.StatusCode, w.Body.String()) +// if tt.expectedStatus > 300 { +// var result httperrors.HTTPErrorResponse +// err := json.NewDecoder(resp.Body).Decode(&result) + +// require.Nil(t, err) +// require.Equal(t, tt.expectedErrorMessage, result.Message) +// } else { +// var result v1.NetworkResponse +// err = json.NewDecoder(resp.Body).Decode(&result) +// require.Nil(t, err) +// require.Equal(t, tt.networkName, *result.Name) +// require.Equal(t, tt.partitionID, *result.PartitionID) +// require.Equal(t, tt.projectID, *result.ProjectID) +// // TODO check af and length +// } +// } +// } diff --git a/cmd/metal-api/internal/service/partition-service.go b/cmd/metal-api/internal/service/partition-service.go index 7d11615d7..efc55ac9d 100644 --- a/cmd/metal-api/internal/service/partition-service.go +++ b/cmd/metal-api/internal/service/partition-service.go @@ -170,14 +170,7 @@ func (r *partitionResource) createPartition(request *restful.Request, response * if requestPayload.Labels != nil { labels = requestPayload.Labels } - prefixLength := uint8(22) - if requestPayload.PrivateNetworkPrefixLength != nil { - prefixLength = uint8(*requestPayload.PrivateNetworkPrefixLength) - if prefixLength < 16 || prefixLength > 30 { - r.sendError(request, response, httperrors.BadRequest(errors.New("private network prefix length is out of range"))) - return - } - } + var imageURL string if requestPayload.PartitionBootConfiguration.ImageURL != nil { imageURL = *requestPayload.PartitionBootConfiguration.ImageURL @@ -211,9 +204,8 @@ func (r *partitionResource) createPartition(request *restful.Request, response * Name: name, Description: description, }, - Labels: labels, - MgmtServiceAddress: mgmtServiceAddress, - PrivateNetworkPrefixLength: prefixLength, + Labels: labels, + MgmtServiceAddress: mgmtServiceAddress, BootConfiguration: metal.BootConfiguration{ ImageURL: imageURL, KernelURL: kernelURL, diff --git a/cmd/metal-api/internal/service/v1/network.go b/cmd/metal-api/internal/service/v1/network.go index d1bb9cc73..eb5e529ac 100644 --- a/cmd/metal-api/internal/service/v1/network.go +++ b/cmd/metal-api/internal/service/v1/network.go @@ -17,6 +17,7 @@ type NetworkBase struct { type NetworkImmutable struct { Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` + ChildPrefixLength *uint8 `json:"childprefixlength" description:"if privatesuper, this defines the bitlen of child prefixes if not nil" optional:"true"` Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` @@ -47,6 +48,29 @@ type NetworkAllocateRequest struct { NetworkBase DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` Nat *bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip" optional:"true"` + AddressFamily *string `json:"address_family" description:"can be ipv4 or ipv6, defaults to ipv4" optional:"true"` + Length *uint8 `json:"length" description:"the bitlen of the prefix to allocate, defaults to childprefixlength of super prefix" optional:"true"` +} + +// 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 } // NetworkFindRequest is used to find a Network with different criteria. @@ -106,6 +130,7 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw NetworkImmutable: NetworkImmutable{ Prefixes: network.Prefixes.String(), DestinationPrefixes: network.DestinationPrefixes.String(), + ChildPrefixLength: network.ChildPrefixLength, Nat: network.Nat, PrivateSuper: network.PrivateSuper, Underlay: network.Underlay, diff --git a/cmd/metal-api/internal/service/v1/partition.go b/cmd/metal-api/internal/service/v1/partition.go index beed5d2fc..e48437797 100644 --- a/cmd/metal-api/internal/service/v1/partition.go +++ b/cmd/metal-api/internal/service/v1/partition.go @@ -5,9 +5,8 @@ import ( ) type PartitionBase struct { - MgmtServiceAddress *string `json:"mgmtserviceaddress" description:"the address to the management service of this partition" optional:"true"` - PrivateNetworkPrefixLength *int `json:"privatenetworkprefixlength" description:"the length of private networks for the machine's child networks in this partition, default 22" optional:"true" minimum:"16" maximum:"30"` - Labels map[string]string `json:"labels" description:"free labels that you associate with this partition" optional:"true"` + MgmtServiceAddress *string `json:"mgmtserviceaddress" description:"the address to the management service of this partition" optional:"true"` + Labels map[string]string `json:"labels" description:"free labels that you associate with this partition" optional:"true"` } type PartitionBootConfiguration struct { @@ -67,8 +66,6 @@ func NewPartitionResponse(p *metal.Partition) *PartitionResponse { return nil } - prefixLength := int(p.PrivateNetworkPrefixLength) - labels := map[string]string{} if p.Labels != nil { labels = p.Labels @@ -86,7 +83,6 @@ func NewPartitionResponse(p *metal.Partition) *PartitionResponse { }, PartitionBase: PartitionBase{ MgmtServiceAddress: &p.MgmtServiceAddress, - PrivateNetworkPrefixLength: &prefixLength, }, PartitionBootConfiguration: PartitionBootConfiguration{ ImageURL: &p.BootConfiguration.ImageURL, diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index 273f5bfb0..5722e1375 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -283,6 +283,8 @@ var ( prefix2 = metal.Prefix{IP: "100.64.2.0", Length: "16"} prefix3 = metal.Prefix{IP: "192.0.0.0", Length: "16"} prefixIPAM = metal.Prefix{IP: "10.0.0.0", Length: "16"} + cpl1 = uint8(28) + cpl2 = uint8(22) prefixes1 = []metal.Prefix{prefix1, prefix2} prefixes2 = []metal.Prefix{prefix2} @@ -295,9 +297,10 @@ var ( Name: "Network 1", Description: "description 1", }, - PartitionID: Partition1.ID, - Prefixes: prefixes1, - PrivateSuper: true, + PartitionID: Partition1.ID, + Prefixes: prefixes1, + PrivateSuper: true, + ChildPrefixLength: &cpl1, } Nw2 = metal.Network{ Base: metal.Base{ @@ -305,8 +308,9 @@ var ( Name: "Network 2", Description: "description 2", }, - Prefixes: prefixes2, - Underlay: true, + Prefixes: prefixes2, + Underlay: true, + ChildPrefixLength: &cpl2, } Nw3 = metal.Network{ Base: metal.Base{ @@ -481,6 +485,13 @@ var ( Tags: []string{tag.MachineID}, ProjectID: "1", } + IP4 = metal.IP{ + IPAddress: "2001:0db8:85a3::1", + Name: "IPv6 4", + Description: "description 4", + Type: "ephemeral", + ProjectID: "1", + } IPAMIP = metal.IP{ Name: "IPAM IP", Description: "description IPAM", @@ -514,7 +525,6 @@ var ( Name: "partition1", Description: "description 1", }, - PrivateNetworkPrefixLength: 22, } Partition2 = metal.Partition{ Base: metal.Base{ @@ -522,7 +532,6 @@ var ( Name: "partition2", Description: "description 2", }, - PrivateNetworkPrefixLength: 22, } Partition3 = metal.Partition{ Base: metal.Base{ @@ -530,7 +539,6 @@ var ( Name: "partition3", Description: "description 3", }, - PrivateNetworkPrefixLength: 22, } // Switches @@ -718,7 +726,7 @@ var ( } // All IPs TestIPs = []metal.IP{ - IP1, IP2, IP3, + IP1, IP2, IP3, IP4, } // All Events @@ -839,6 +847,7 @@ func InitMockDBData(mock *r.Mock) { mock.On(r.DB("mockdb").Table("ip").Get("3.4.5.6")).Return(IP3, nil) mock.On(r.DB("mockdb").Table("ip").Get("8.8.8.8")).Return(nil, errors.New("Test Error")) mock.On(r.DB("mockdb").Table("ip").Get("9.9.9.9")).Return(nil, nil) + mock.On(r.DB("mockdb").Table("ip").Get("2001:0db8:85a3::1")).Return(IP4, nil) mock.On(r.DB("mockdb").Table("ip").Get(Partition1InternetIP.IPAddress)).Return(Partition1InternetIP, nil) mock.On(r.DB("mockdb").Table("ip").Get(Partition2InternetIP.IPAddress)).Return(Partition2InternetIP, nil) mock.On(r.DB("mockdb").Table("ip").Get(Partition1SpecificSharedIP.IPAddress)).Return(Partition1SpecificSharedIP, nil) diff --git a/spec/metal-api.json b/spec/metal-api.json index 867f4fba7..23f98a60e 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -3565,6 +3565,10 @@ }, "v1.NetworkAllocateRequest": { "properties": { + "address_family": { + "description": "can be ipv4 or ipv6, defaults to ipv4", + "type": "string" + }, "description": { "description": "a description for this entity", "type": "string" @@ -3583,6 +3587,11 @@ "description": "free labels that you associate with this network.", "type": "object" }, + "length": { + "description": "the bitlen of the prefix to allocate, defaults to childprefixlength of super prefix", + "format": "byte", + "type": "integer" + }, "name": { "description": "a readable name for this entity", "type": "string" @@ -3630,6 +3639,11 @@ }, "v1.NetworkCreateRequest": { "properties": { + "childprefixlength": { + "description": "if privatesuper, this defines the bitlen of child prefixes if not nil", + "format": "byte", + "type": "integer" + }, "description": { "description": "a description for this entity", "type": "string" @@ -3763,6 +3777,11 @@ "v1.NetworkImmutable": { "description": "a network which contains prefixes from which IP addresses can be allocated\nprefixes that are reachable within this network", "properties": { + "childprefixlength": { + "description": "if privatesuper, this defines the bitlen of child prefixes if not nil", + "format": "byte", + "type": "integer" + }, "destinationprefixes": { "description": "the destination prefixes of this network", "items": { @@ -3819,6 +3838,11 @@ "readOnly": true, "type": "string" }, + "childprefixlength": { + "description": "if privatesuper, this defines the bitlen of child prefixes if not nil", + "format": "byte", + "type": "integer" + }, "created": { "description": "the creation time of this entity", "format": "date-time", @@ -4008,13 +4032,6 @@ "mgmtserviceaddress": { "description": "the address to the management service of this partition", "type": "string" - }, - "privatenetworkprefixlength": { - "description": "the length of private networks for the machine's child networks in this partition, default 22", - "format": "int32", - "maximum": 30, - "minimum": 16, - "type": "integer" } } }, @@ -4102,13 +4119,6 @@ "name": { "description": "a readable name for this entity", "type": "string" - }, - "privatenetworkprefixlength": { - "description": "the length of private networks for the machine's child networks in this partition, default 22", - "format": "int32", - "maximum": 30, - "minimum": 16, - "type": "integer" } }, "required": [ @@ -4156,13 +4166,6 @@ "name": { "description": "a readable name for this entity", "type": "string" - }, - "privatenetworkprefixlength": { - "description": "the length of private networks for the machine's child networks in this partition, default 22", - "format": "int32", - "maximum": 30, - "minimum": 16, - "type": "integer" } }, "required": [ From 22d916e824fea5175a78453abb669d68e01ed1e0 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 4 Jul 2024 13:09:45 +0200 Subject: [PATCH 02/12] More tests --- .../internal/service/network-service.go | 33 ++- .../internal/service/network-service_test.go | 226 ++++++++++-------- cmd/metal-api/internal/testdata/testdata.go | 3 +- 3 files changed, 157 insertions(+), 105 deletions(-) diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index a1a31357e..8f8c506d1 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -20,6 +20,7 @@ import ( v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" "github.com/metal-stack/metal-lib/auditing" "github.com/metal-stack/metal-lib/httperrors" + "github.com/metal-stack/metal-lib/pkg/pointer" ) type networkResource struct { @@ -263,6 +264,7 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest prefixes := metal.Prefixes{} addressFamilies := make(map[string]bool) + var addressFamily v1.AddressFamily // all Prefixes must be valid and from the same addressfamily for i := range requestPayload.Prefixes { p := requestPayload.Prefixes[i] @@ -278,9 +280,11 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest } if ipprefix.Addr().Is4() { addressFamilies["ipv4"] = true + addressFamily = v1.IPv4AddressFamily } if ipprefix.Addr().Is6() { addressFamilies["ipv6"] = true + addressFamily = v1.IPv6AddressFamily } prefixes = append(prefixes, *prefix) } @@ -290,6 +294,17 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } + if privateSuper && requestPayload.ChildPrefixLength == nil { + var childprefixlength *uint8 + if addressFamily == v1.IPv4AddressFamily { + childprefixlength = pointer.Pointer(uint8(22)) + } + if addressFamily == v1.IPv6AddressFamily { + childprefixlength = pointer.Pointer(uint8(64)) + } + r.log.Info("createnetwork childprefixlength not set for private super network, using default", "addressfamily", addressFamily, "childprefixlength", childprefixlength) + } + destPrefixes := metal.Prefixes{} for i := range requestPayload.DestinationPrefixes { p := requestPayload.DestinationPrefixes[i] @@ -474,6 +489,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re return } + r.log.Info("allocateNetwork", "request", requestPayload) var name string if requestPayload.Name != nil { name = *requestPayload.Name @@ -520,14 +536,6 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re return } - var superNetworks metal.Networks - boolTrue := true - err = r.ds.SearchNetworks(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: &boolTrue}, &superNetworks) - if err != nil { - r.sendError(request, response, defaultError(err)) - return - } - destPrefixes := metal.Prefixes{} for _, p := range requestPayload.DestinationPrefixes { prefix, err := metal.NewPrefixFromCIDR(p) @@ -547,8 +555,16 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re r.log.Info("network allocate", "family", addressFamily) var ( superNetwork metal.Network + superNetworks metal.Networks superNetworkFound bool ) + + err = r.ds.SearchNetworks(&datastore.NetworkSearchQuery{PartitionID: &partition.ID, PrivateSuper: pointer.Pointer(true)}, &superNetworks) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + for _, snw := range superNetworks { ipprefix, err := netip.ParsePrefix(snw.Prefixes[0].String()) if err != nil { @@ -592,6 +608,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re if requestPayload.Length != nil { length = *requestPayload.Length } + r.log.Info("network allocate", "supernetwork", superNetwork.Name, "length", length) ctx := request.Request.Context() nw, err := createChildNetwork(ctx, r.ds, r.ipamer, nwSpec, &superNetwork, length) diff --git a/cmd/metal-api/internal/service/network-service_test.go b/cmd/metal-api/internal/service/network-service_test.go index c57ee945d..3f1bb9771 100644 --- a/cmd/metal-api/internal/service/network-service_test.go +++ b/cmd/metal-api/internal/service/network-service_test.go @@ -7,8 +7,12 @@ import ( "log/slog" "net/http" "net/http/httptest" + "net/netip" "testing" + mdmv1 "github.com/metal-stack/masterdata-api/api/v1" + mdmv1mock "github.com/metal-stack/masterdata-api/api/v1/mocks" + mdm "github.com/metal-stack/masterdata-api/pkg/client" "github.com/metal-stack/metal-lib/httperrors" "github.com/metal-stack/metal-lib/pkg/pointer" r "gopkg.in/rethinkdb/rethinkdb-go.v6" @@ -21,6 +25,7 @@ import ( restful "github.com/emicklei/go-restful/v3" "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" + testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -420,99 +425,128 @@ func Test_networkResource_createNetwork(t *testing.T) { } } -// func Test_networkResource_allocateNetwork(t *testing.T) { -// log := slog.Default() -// tests := []struct { -// name string -// networkName string -// partitionID string -// projectID string -// childprefixlength *uint8 -// addressFamily *string -// shared bool -// expectedStatus int -// expectedErrorMessage string -// }{ -// { -// name: "simple ipv4", -// networkName: "tenantv4", -// partitionID: "1", -// projectID: "project-1", -// expectedStatus: http.StatusCreated, -// }, -// { -// name: "ipv6 without ipv6 super", -// networkName: "tenantv6", -// partitionID: "1", -// projectID: "project-1", -// addressFamily: pointer.Pointer("ipv6"), -// expectedStatus: http.StatusUnprocessableEntity, -// expectedErrorMessage: "no supernetwork for addressfamily:IPv6 found", -// }, -// } -// for _, tt := range tests { -// ds, mock := datastore.InitMockDB(t) - -// ipamer, err := testdata.InitMockIpamData(mock, false) -// require.Nil(t, err) -// mock.On(r.DB("mockdb").Table("network").Filter(r.MockAnything()).Filter(r.MockAnything())).Return(metal.Networks{testdata.Nw1, testdata.Nw2}, nil) -// changes := []r.ChangeResponse{{OldValue: map[string]interface{}{"id": float64(42)}}} -// mock.On(r.DB("mockdb").Table("integerpool").Limit(1).Delete(r. -// DeleteOpts{ReturnChanges: true})).Return(r.WriteResponse{Changes: changes}, nil) - -// mock.On(r.DB("mockdb").Table("partition").Get(r.MockAnything())).Return( -// metal.Partition{ -// Base: metal.Base{ID: tt.partitionID}, -// }, -// nil, -// ) -// testdata.InitMockDBData(mock) - -// psc := mdmock.ProjectServiceClient{} -// psc.On("Get", context.Background(), &mdmv1.ProjectGetRequest{Id: "project-1"}).Return(&mdmv1.ProjectResponse{ -// Project: &mdmv1.Project{ -// Meta: &mdmv1.Meta{Id: tt.projectID}, -// }, -// }, nil, -// ) -// tsc := mdmock.TenantServiceClient{} - -// mdc := mdm.NewMock(&psc, &tsc) - -// networkservice := NewNetwork(log, ds, ipamer, mdc) -// container := restful.NewContainer().Add(networkservice) - -// allocateRequest := &v1.NetworkAllocateRequest{ -// Describable: v1.Describable{Name: &tt.networkName}, -// NetworkBase: v1.NetworkBase{PartitionID: &tt.partitionID, ProjectID: &tt.projectID}, -// AddressFamily: tt.addressFamily, -// Length: tt.childprefixlength, -// } - -// js, _ := json.Marshal(allocateRequest) -// body := bytes.NewBuffer(js) -// req := httptest.NewRequest("POST", "/v1/network/allocate", body) -// req.Header.Add("Content-Type", "application/json") -// container = injectAdmin(log, container, req) -// w := httptest.NewRecorder() -// container.ServeHTTP(w, req) - -// resp := w.Result() -// require.Equal(t, tt.expectedStatus, resp.StatusCode, w.Body.String()) -// if tt.expectedStatus > 300 { -// var result httperrors.HTTPErrorResponse -// err := json.NewDecoder(resp.Body).Decode(&result) - -// require.Nil(t, err) -// require.Equal(t, tt.expectedErrorMessage, result.Message) -// } else { -// var result v1.NetworkResponse -// err = json.NewDecoder(resp.Body).Decode(&result) -// require.Nil(t, err) -// require.Equal(t, tt.networkName, *result.Name) -// require.Equal(t, tt.partitionID, *result.PartitionID) -// require.Equal(t, tt.projectID, *result.ProjectID) -// // TODO check af and length -// } -// } -// } +func Test_networkResource_allocateNetwork(t *testing.T) { + log := slog.Default() + tests := []struct { + name string + networkName string + partitionID string + projectID string + childprefixlength *uint8 + addressFamily *string + shared bool + expectedStatus int + expectedErrorMessage string + }{ + { + name: "simple ipv4, default childprefixlength", + networkName: "tenantv4", + partitionID: testdata.Partition1.ID, + projectID: "project-1", + expectedStatus: http.StatusCreated, + }, + { + name: "simple ipv4, specific childprefixlength", + networkName: "tenantv4.2", + partitionID: testdata.Partition1.ID, + projectID: "project-1", + childprefixlength: pointer.Pointer(uint8(29)), + expectedStatus: http.StatusCreated, + }, + { + name: "ipv6 without ipv6 super", + networkName: "tenantv6", + partitionID: testdata.Partition1.ID, + projectID: "project-1", + addressFamily: pointer.Pointer("ipv6"), + expectedStatus: http.StatusBadRequest, + expectedErrorMessage: "no supernetwork for addressfamily:IPv6 found", + }, + } + for _, tt := range tests { + ds, mock := datastore.InitMockDB(t) + + supernetwork := testdata.Nw1 + ipamer, err := testdata.InitMockIpamData(mock, false) + require.NoError(t, err) + mock.On(r.DB("mockdb").Table("network").Filter(r.MockAnything()).Filter(r.MockAnything())).Return(metal.Networks{supernetwork}, nil) + changes := []r.ChangeResponse{{OldValue: map[string]interface{}{"id": float64(42)}}} + mock.On(r.DB("mockdb").Table("integerpool").Limit(1).Delete(r. + DeleteOpts{ReturnChanges: true})).Return(r.WriteResponse{Changes: changes}, nil) + + mock.On(r.DB("mockdb").Table("partition").Get(r.MockAnything())).Return( + metal.Partition{ + Base: metal.Base{ID: tt.partitionID}, + }, + nil, + ) + testdata.InitMockDBData(mock) + + psc := mdmv1mock.ProjectServiceClient{} + psc.On("Get", testifymock.Anything, &mdmv1.ProjectGetRequest{Id: "project-1"}).Return(&mdmv1.ProjectResponse{ + Project: &mdmv1.Project{ + Meta: &mdmv1.Meta{Id: tt.projectID}, + }, + }, nil, + ) + tsc := mdmv1mock.TenantServiceClient{} + + mdc := mdm.NewMock(&psc, &tsc, nil, nil) + + networkservice := NewNetwork(log, ds, ipamer, mdc) + container := restful.NewContainer().Add(networkservice) + + allocateRequest := &v1.NetworkAllocateRequest{ + Describable: v1.Describable{Name: &tt.networkName}, + NetworkBase: v1.NetworkBase{PartitionID: &tt.partitionID, ProjectID: &tt.projectID}, + AddressFamily: tt.addressFamily, + Length: tt.childprefixlength, + } + + js, err := json.Marshal(allocateRequest) + require.NoError(t, err) + + body := bytes.NewBuffer(js) + req := httptest.NewRequest("POST", "/v1/network/allocate", body) + req.Header.Add("Content-Type", "application/json") + container = injectAdmin(log, container, req) + w := httptest.NewRecorder() + container.ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + require.Equal(t, tt.expectedStatus, resp.StatusCode, w.Body.String()) + if tt.expectedStatus > 300 { + var result httperrors.HTTPErrorResponse + err := json.NewDecoder(resp.Body).Decode(&result) + + require.NoError(t, err) + require.Equal(t, tt.expectedErrorMessage, result.Message) + } else { + var result v1.NetworkResponse + err = json.NewDecoder(resp.Body).Decode(&result) + + requestAF := "ipv4" + if tt.addressFamily != nil { + requestAF = "ipv6" + } + + require.GreaterOrEqual(t, len(result.Prefixes), 1) + resultFirstPrefix := netip.MustParsePrefix(result.Prefixes[0]) + af := "ipv4" + if resultFirstPrefix.Addr().Is6() { + af = "ipv6" + } + expectedLength := *supernetwork.ChildPrefixLength + if tt.childprefixlength != nil { + expectedLength = *tt.childprefixlength + } + require.NoError(t, err) + require.Equal(t, tt.networkName, *result.Name) + require.Equal(t, tt.partitionID, *result.PartitionID) + require.Equal(t, tt.projectID, *result.ProjectID) + require.Equal(t, requestAF, af) + require.Equal(t, int(expectedLength), resultFirstPrefix.Bits()) + } + } +} diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index 5722e1375..6548e05e1 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -280,7 +280,7 @@ var ( } // Networks prefix1 = metal.Prefix{IP: "185.1.2.0", Length: "26"} - prefix2 = metal.Prefix{IP: "100.64.2.0", Length: "16"} + prefix2 = metal.Prefix{IP: "100.64.0.0", Length: "16"} prefix3 = metal.Prefix{IP: "192.0.0.0", Length: "16"} prefixIPAM = metal.Prefix{IP: "10.0.0.0", Length: "16"} cpl1 = uint8(28) @@ -308,6 +308,7 @@ var ( Name: "Network 2", Description: "description 2", }, + PartitionID: Partition1.ID, Prefixes: prefixes2, Underlay: true, ChildPrefixLength: &cpl2, From e8571c9e64256f9486c6ba2aa7ca7c5b846c10fd Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Sun, 7 Jul 2024 09:08:39 +0200 Subject: [PATCH 03/12] Validate Prefixes and DestinationPrefixes on create and update network --- .../migrations/06_childprefixlength.go | 1 + .../internal/service/network-service.go | 92 ++++++++++--------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go index 11526c805..7d08220fa 100644 --- a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go +++ b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go @@ -34,6 +34,7 @@ func init() { return err } + // TODO: does not work somehow new := old new.ChildPrefixLength = &partition.PrivateNetworkPrefixLength err = rs.UpdateNetwork(&old, &new) diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index 8f8c506d1..43089d7a0 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -262,35 +262,16 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } - prefixes := metal.Prefixes{} - addressFamilies := make(map[string]bool) - var addressFamily v1.AddressFamily // all Prefixes must be valid and from the same addressfamily - for i := range requestPayload.Prefixes { - p := requestPayload.Prefixes[i] - prefix, err := metal.NewPrefixFromCIDR(p) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) - return - } - ipprefix, err := netip.ParsePrefix(p) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) - return - } - if ipprefix.Addr().Is4() { - addressFamilies["ipv4"] = true - addressFamily = v1.IPv4AddressFamily - } - if ipprefix.Addr().Is6() { - addressFamilies["ipv6"] = true - addressFamily = v1.IPv6AddressFamily - } - prefixes = append(prefixes, *prefix) + prefixes, addressFamily, err := validatePrefixes(requestPayload.Prefixes) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return } - - if len(addressFamilies) > 1 { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefixes have different addressfamilies"))) + // all DestinationPrefixes must be valid and from the same addressfamily + _, _, err = validatePrefixes(requestPayload.DestinationPrefixes) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) return } @@ -480,6 +461,37 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage)) } +func validatePrefixes(prefixes []string) (metal.Prefixes, v1.AddressFamily, error) { + var ( + result metal.Prefixes + addressFamilies = make(map[string]bool) + addressFamily v1.AddressFamily + ) + for _, p := range prefixes { + prefix, err := metal.NewPrefixFromCIDR(p) + if err != nil { + return nil, v1.IPv4AddressFamily, fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err) + } + ipprefix, err := netip.ParsePrefix(p) + if err != nil { + return nil, v1.IPv4AddressFamily, fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err) + } + if ipprefix.Addr().Is4() { + addressFamilies["ipv4"] = true + addressFamily = v1.IPv4AddressFamily + } + if ipprefix.Addr().Is6() { + addressFamilies["ipv6"] = true + addressFamily = v1.IPv6AddressFamily + } + result = append(result, *prefix) + } + if len(addressFamilies) > 1 { + return nil, v1.IPv4AddressFamily, fmt.Errorf("given prefixes have different addressfamilies") + } + return result, addressFamily, nil +} + // TODO add possibility to allocate from a non super network if given in the AllocateRequest and super has childprefixlength func (r *networkResource) allocateNetwork(request *restful.Request, response *restful.Response) { var requestPayload v1.NetworkAllocateRequest @@ -736,12 +748,15 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest var prefixesToBeAdded metal.Prefixes if len(requestPayload.Prefixes) > 0 { - newNetwork.Prefixes, err = prefixesFromCidr(requestPayload.Prefixes) + // all Prefixes must be valid and from the same addressfamily + prefixes, _, err := validatePrefixes(requestPayload.Prefixes) if err != nil { - r.sendError(request, response, defaultError(err)) + r.sendError(request, response, httperrors.BadRequest(err)) return } + newNetwork.Prefixes = prefixes + prefixesToBeRemoved = oldNetwork.SubtractPrefixes(newNetwork.Prefixes...) // now validate if there are ips which have a prefix to be removed as a parent @@ -779,11 +794,14 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest } if len(requestPayload.DestinationPrefixes) > 0 { - newNetwork.DestinationPrefixes, err = prefixesFromCidr(requestPayload.DestinationPrefixes) + // all Prefixes must be valid and from the same addressfamily + prefixes, _, err := validatePrefixes(requestPayload.Prefixes) if err != nil { - r.sendError(request, response, defaultError(err)) + r.sendError(request, response, httperrors.BadRequest(err)) return } + + newNetwork.DestinationPrefixes = prefixes } err = r.ds.UpdateNetwork(oldNetwork, &newNetwork) @@ -797,18 +815,6 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusOK, v1.NewNetworkResponse(&newNetwork, usage)) } -func prefixesFromCidr(PrefixesCidr []string) (metal.Prefixes, error) { - var prefixes metal.Prefixes - for _, prefixCidr := range PrefixesCidr { - Prefix, err := metal.NewPrefixFromCIDR(prefixCidr) - if err != nil { - return nil, err - } - prefixes = append(prefixes, *Prefix) - } - return prefixes, nil -} - func (r *networkResource) deleteNetwork(request *restful.Request, response *restful.Response) { id := request.PathParameter("id") From 59c75112e6ca84cdae86c8178f86e5b894e72291 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Sun, 7 Jul 2024 09:11:40 +0200 Subject: [PATCH 04/12] Constify defaultChildPrefixlength --- .../migrate_integration_test.go | 99 ++++++++++++++++++- cmd/metal-api/internal/datastore/rethinkdb.go | 5 + .../internal/service/network-service.go | 53 ++++++++-- .../internal/service/network-service_test.go | 62 ++++++++++-- cmd/metal-api/internal/service/v1/network.go | 4 +- spec/metal-api.json | 6 +- 6 files changed, 212 insertions(+), 17 deletions(-) diff --git a/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go b/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go index c42b553ab..48820f295 100644 --- a/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go +++ b/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go @@ -5,6 +5,7 @@ package migrations_integration import ( "context" + "fmt" "log/slog" "os" "time" @@ -15,6 +16,7 @@ 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" @@ -22,7 +24,7 @@ import ( "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() { @@ -125,3 +127,98 @@ 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", + PrivateSuper: true, + } + n2 = &metal.Network{ + Base: metal.Base{ + ID: "n2", + }, + PartitionID: "p2", + PrivateSuper: true, + } + n3 = &metal.Network{ + Base: metal.Base{ + ID: "n3", + }, + 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.ChildPrefixLength, fmt.Sprintf("childprefixlength:%d", *n1fetched.ChildPrefixLength)) + + n2fetched, err := rs.FindNetworkByID(n2.ID) + require.NoError(t, err) + require.NotNil(t, n2fetched) + require.Equal(t, p2.PrivateNetworkPrefixLength, *n2fetched.ChildPrefixLength, fmt.Sprintf("childprefixlength:%d", *n2fetched.ChildPrefixLength)) + + n3fetched, err := rs.FindNetworkByID(n3.ID) + require.NoError(t, err) + require.NotNil(t, n3fetched) + require.Nil(t, n3fetched.ChildPrefixLength) +} diff --git a/cmd/metal-api/internal/datastore/rethinkdb.go b/cmd/metal-api/internal/datastore/rethinkdb.go index 38cbc07ca..6564278a7 100644 --- a/cmd/metal-api/internal/datastore/rethinkdb.go +++ b/cmd/metal-api/internal/datastore/rethinkdb.go @@ -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 { diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index 43089d7a0..fd8e37949 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -23,6 +23,11 @@ import ( "github.com/metal-stack/metal-lib/pkg/pointer" ) +const ( + ipv4DefaultChildPrefixLength = uint8(22) + ipv6DefaultChildPrefixLength = uint8(64) +) + type networkResource struct { webResource ipamer ipam.IPAMer @@ -278,10 +283,10 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest if privateSuper && requestPayload.ChildPrefixLength == nil { var childprefixlength *uint8 if addressFamily == v1.IPv4AddressFamily { - childprefixlength = pointer.Pointer(uint8(22)) + childprefixlength = pointer.Pointer(ipv4DefaultChildPrefixLength) } if addressFamily == v1.IPv6AddressFamily { - childprefixlength = pointer.Pointer(uint8(64)) + childprefixlength = pointer.Pointer(ipv6DefaultChildPrefixLength) } r.log.Info("createnetwork childprefixlength not set for private super network, using default", "addressfamily", addressFamily, "childprefixlength", childprefixlength) } @@ -461,6 +466,24 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage)) } +func getAddressFamily(prefixes metal.Prefixes) (*v1.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(v1.IPv4AddressFamily), nil + } + if parsed.Addr().Is6() { + return pointer.Pointer(v1.IPv6AddressFamily), nil + } + return nil, fmt.Errorf("unable to detect addressfamily from prefixes:%v", prefixes.String()) +} + func validatePrefixes(prefixes []string) (metal.Prefixes, v1.AddressFamily, error) { var ( result metal.Prefixes @@ -724,6 +747,12 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest return } + addressFamily, err := getAddressFamily(oldNetwork.Prefixes) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + newNetwork := *oldNetwork if requestPayload.Name != nil { @@ -744,17 +773,24 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest return } - var prefixesToBeRemoved metal.Prefixes - var prefixesToBeAdded metal.Prefixes + var ( + prefixesToBeRemoved metal.Prefixes + prefixesToBeAdded metal.Prefixes + ) if len(requestPayload.Prefixes) > 0 { // all Prefixes must be valid and from the same addressfamily - prefixes, _, err := validatePrefixes(requestPayload.Prefixes) + prefixes, af, err := validatePrefixes(requestPayload.Prefixes) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return } + if af != *addressFamily { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("new prefixes have different addressfamily %q then existing prefixes %q", af, *addressFamily))) + return + } + newNetwork.Prefixes = prefixes prefixesToBeRemoved = oldNetwork.SubtractPrefixes(newNetwork.Prefixes...) @@ -795,12 +831,17 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest if len(requestPayload.DestinationPrefixes) > 0 { // all Prefixes must be valid and from the same addressfamily - prefixes, _, err := validatePrefixes(requestPayload.Prefixes) + prefixes, af, err := validatePrefixes(requestPayload.Prefixes) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return } + if af != *addressFamily { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("new destination prefixes have different addressfamily %q then existing destination prefixes %q", af, *addressFamily))) + return + } + newNetwork.DestinationPrefixes = prefixes } diff --git a/cmd/metal-api/internal/service/network-service_test.go b/cmd/metal-api/internal/service/network-service_test.go index 3f1bb9771..9a7d8238b 100644 --- a/cmd/metal-api/internal/service/network-service_test.go +++ b/cmd/metal-api/internal/service/network-service_test.go @@ -8,25 +8,23 @@ import ( "net/http" "net/http/httptest" "net/netip" + "reflect" "testing" + restful "github.com/emicklei/go-restful/v3" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" mdmv1mock "github.com/metal-stack/masterdata-api/api/v1/mocks" mdm "github.com/metal-stack/masterdata-api/pkg/client" - "github.com/metal-stack/metal-lib/httperrors" - "github.com/metal-stack/metal-lib/pkg/pointer" - 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/ipam" - "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" - - restful "github.com/emicklei/go-restful/v3" "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" + "github.com/metal-stack/metal-lib/httperrors" + "github.com/metal-stack/metal-lib/pkg/pointer" testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + r "gopkg.in/rethinkdb/rethinkdb-go.v6" ) func TestGetNetworks(t *testing.T) { @@ -550,3 +548,53 @@ func Test_networkResource_allocateNetwork(t *testing.T) { } } } + +func Test_getAddressFamily(t *testing.T) { + tests := []struct { + name string + prefixes metal.Prefixes + want *v1.AddressFamily + wantErr bool + }{ + { + name: "ipv4", + prefixes: metal.Prefixes{ + metal.Prefix{IP: "10.0.0.0", Length: "8"}, + }, + want: pointer.Pointer(v1.IPv4AddressFamily), + }, + { + name: "ipv6", + prefixes: metal.Prefixes{ + metal.Prefix{IP: "2001::", Length: "64"}, + }, + want: pointer.Pointer(v1.IPv6AddressFamily), + }, + { + name: "empty prefixes", + prefixes: metal.Prefixes{}, + want: nil, + wantErr: false, + }, + { + name: "malformed ipv4", + prefixes: metal.Prefixes{ + metal.Prefix{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/v1/network.go b/cmd/metal-api/internal/service/v1/network.go index eb5e529ac..c1362d624 100644 --- a/cmd/metal-api/internal/service/v1/network.go +++ b/cmd/metal-api/internal/service/v1/network.go @@ -48,8 +48,8 @@ type NetworkAllocateRequest struct { NetworkBase DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` Nat *bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip" optional:"true"` - AddressFamily *string `json:"address_family" description:"can be ipv4 or ipv6, defaults to ipv4" optional:"true"` - Length *uint8 `json:"length" description:"the bitlen of the prefix to allocate, defaults to childprefixlength of super prefix" optional:"true"` + AddressFamily *string `json:"address_family" description:"can be ipv4 or ipv6, defaults to ipv4"` + Length *uint8 `json:"length" description:"the bitlen of the prefix to allocate, defaults to childprefixlength of super prefix"` } // AddressFamily identifies IPv4/IPv6 diff --git a/spec/metal-api.json b/spec/metal-api.json index 23f98a60e..897b54824 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -3612,7 +3612,11 @@ "description": "marks a network as shareable.", "type": "boolean" } - } + }, + "required": [ + "address_family", + "length" + ] }, "v1.NetworkBase": { "properties": { From aae9274d5cb63dff72f7eff1c1cbada59b2eecd9 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 17 Jul 2024 15:57:31 +0200 Subject: [PATCH 05/12] Support DualStack Networks --- .../migrations/06_childprefixlength.go | 10 +- .../migrate_integration_test.go | 10 +- cmd/metal-api/internal/datastore/network.go | 31 +- cmd/metal-api/internal/ipam/ipam.go | 32 +- cmd/metal-api/internal/metal/network.go | 21 +- .../internal/service/integration_test.go | 6 +- cmd/metal-api/internal/service/ip-service.go | 40 ++- .../internal/service/ip-service_test.go | 32 ++ .../internal/service/machine-service.go | 50 +-- .../machine-service_allocation_test.go | 2 +- .../internal/service/network-service.go | 325 ++++++++++-------- .../internal/service/network-service_test.go | 166 +++++---- cmd/metal-api/internal/service/v1/ip.go | 3 +- cmd/metal-api/internal/service/v1/network.go | 77 ++--- cmd/metal-api/internal/testdata/ipam.go | 3 +- cmd/metal-api/internal/testdata/testdata.go | 79 +++-- spec/metal-api.json | 95 ++--- 17 files changed, 564 insertions(+), 418 deletions(-) diff --git a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go index b45ab3861..f7d569616 100644 --- a/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go +++ b/cmd/metal-api/internal/datastore/migrations/06_childprefixlength.go @@ -39,10 +39,16 @@ func init() { return err } if af != nil { - new.AddressFamily = *af + if new.AddressFamilies == nil { + new.AddressFamilies = make(map[metal.AddressFamily]bool) + } + new.AddressFamilies[*af] = true } if new.PrivateSuper { - new.DefaultChildPrefixLength = &partition.PrivateNetworkPrefixLength + if new.DefaultChildPrefixLength == nil { + new.DefaultChildPrefixLength = make(map[metal.AddressFamily]uint8) + } + new.DefaultChildPrefixLength[*af] = partition.PrivateNetworkPrefixLength } err = rs.UpdateNetwork(&old, &new) if err != nil { diff --git a/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go b/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go index 53d54cd7c..7114268e1 100644 --- a/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go +++ b/cmd/metal-api/internal/datastore/migrations_integration/migrate_integration_test.go @@ -219,18 +219,18 @@ func Test_MigrationChildPrefixLength(t *testing.T) { 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) + require.Equal(t, p1.PrivateNetworkPrefixLength, n1fetched.DefaultChildPrefixLength[metal.IPv4AddressFamily], fmt.Sprintf("childprefixlength:%v", n1fetched.DefaultChildPrefixLength)) + require.True(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, fmt.Sprintf("childprefixlength:%d", *n2fetched.DefaultChildPrefixLength)) - require.Equal(t, metal.IPv6AddressFamily, n2fetched.AddressFamily) + require.Equal(t, p2.PrivateNetworkPrefixLength, n2fetched.DefaultChildPrefixLength[metal.IPv6AddressFamily], fmt.Sprintf("childprefixlength:%v", n2fetched.DefaultChildPrefixLength)) + require.True(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.Equal(t, metal.IPv4AddressFamily, n3fetched.AddressFamily) + require.True(t, n3fetched.AddressFamilies[metal.IPv4AddressFamily]) } diff --git a/cmd/metal-api/internal/datastore/network.go b/cmd/metal-api/internal/datastore/network.go index 4e949adb3..1e9990e70 100644 --- a/cmd/metal-api/internal/datastore/network.go +++ b/cmd/metal-api/internal/datastore/network.go @@ -12,19 +12,18 @@ 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"` - AddressFamily *metal.AddressFamily `json:"addressfamily" 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"` } func (p *NetworkSearchQuery) Validate() error { @@ -105,12 +104,6 @@ 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 diff --git a/cmd/metal-api/internal/ipam/ipam.go b/cmd/metal-api/internal/ipam/ipam.go index df475ae80..f820187b5 100644 --- a/cmd/metal-api/internal/ipam/ipam.go +++ b/cmd/metal-api/internal/ipam/ipam.go @@ -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/rest" @@ -54,7 +55,7 @@ func (i *ipam) AllocateChildPrefix(ctx context.Context, parentPrefix metal.Prefi return nil, fmt.Errorf("error creating new prefix in ipam: %w", 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) } @@ -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 := map[metal.AddressFamily]uint64{ + af: usage.Msg.AvailableIps, + } + usedIPs := map[metal.AddressFamily]uint64{ + af: usage.Msg.AcquiredIps, + } + availablePrefixes := map[metal.AddressFamily]uint64{ + af: usage.Msg.AvailableSmallestPrefixes, + } + usedPrefixes := map[metal.AddressFamily]uint64{ + 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 } diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 8f54b00d3..f1b2e53b7 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -174,17 +174,17 @@ type Prefix struct { type Prefixes []Prefix // NewPrefixFromCIDR returns a new prefix from a given cidr. -func NewPrefixFromCIDR(cidr string) (*Prefix, error) { +func NewPrefixFromCIDR(cidr string) (*Prefix, *netip.Prefix, error) { prefix, err := netip.ParsePrefix(cidr) if err != nil { - return nil, err + return nil, nil, err } ip := prefix.Addr().String() length := strconv.Itoa(prefix.Bits()) return &Prefix{ IP: ip, Length: length, - }, nil + }, &prefix, nil } // String implements the Stringer interface @@ -211,7 +211,7 @@ type Network struct { Base 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"` + DefaultChildPrefixLength ChildPrefixLength `rethinkdb:"defaultchildprefixlength" json:"childprefixlength" description:"if privatesuper, this defines the bitlen of child prefixes per addressfamily if not nil"` PartitionID string `rethinkdb:"partitionid" json:"partitionid"` ProjectID string `rethinkdb:"projectid" json:"projectid"` ParentNetworkID string `rethinkdb:"parentnetworkid" json:"parentnetworkid"` @@ -221,11 +221,14 @@ type Network struct { 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"` + AddressFamilies AddressFamilies `rethinkdb:"addressfamily" json:"addressfamily"` } +type ChildPrefixLength map[AddressFamily]uint8 + // AddressFamily identifies IPv4/IPv6 type AddressFamily string +type AddressFamilies map[AddressFamily]bool const ( // IPv4AddressFamily identifies IPv4 @@ -253,10 +256,10 @@ type NetworkMap map[string]Network // NetworkUsage contains usage information of a network type NetworkUsage struct { - 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"` + AvailableIPs map[AddressFamily]uint64 `json:"available_ips" description:"the total available IPs" readonly:"true"` + UsedIPs map[AddressFamily]uint64 `json:"used_ips" description:"the total used IPs" readonly:"true"` + AvailablePrefixes map[AddressFamily]uint64 `json:"available_prefixes" description:"the total available 2 bit Prefixes" readonly:"true"` + UsedPrefixes map[AddressFamily]uint64 `json:"used_prefixes" description:"the total used Prefixes" readonly:"true"` } // ByID creates an indexed map of partitions where the id is the index. diff --git a/cmd/metal-api/internal/service/integration_test.go b/cmd/metal-api/internal/service/integration_test.go index 315030e23..1d2f0e5d8 100644 --- a/cmd/metal-api/internal/service/integration_test.go +++ b/cmd/metal-api/internal/service/integration_test.go @@ -23,7 +23,6 @@ import ( metalgrpc "github.com/metal-stack/metal-api/cmd/metal-api/internal/grpc" "github.com/metal-stack/metal-api/test" "github.com/metal-stack/metal-lib/bus" - "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/security" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" @@ -297,8 +296,8 @@ func createTestEnvironment(t *testing.T) testEnv { NetworkImmutable: v1.NetworkImmutable{ Prefixes: []string{testPrivateSuperCidr}, PrivateSuper: true, - DefaultChildPrefixLength: pointer.Pointer(uint8(22)), - AddressFamily: v1.IPv4AddressFamily, + DefaultChildPrefixLength: map[metal.AddressFamily]uint8{metal.IPv4AddressFamily: 22}, + AddressFamilies: map[metal.AddressFamily]bool{metal.IPv4AddressFamily: true}, }, } log.Info("try to create a network", "request", ncr) @@ -323,7 +322,6 @@ func createTestEnvironment(t *testing.T) testEnv { ProjectID: &projectID, PartitionID: &partition.ID, }, - AddressFamily: pointer.Pointer("ipv4"), } status = te.networkAcquire(t, nar, &acquiredPrivateNetwork) require.Equal(t, http.StatusCreated, status) diff --git a/cmd/metal-api/internal/service/ip-service.go b/cmd/metal-api/internal/service/ip-service.go index 75458b989..a08913851 100644 --- a/cmd/metal-api/internal/service/ip-service.go +++ b/cmd/metal-api/internal/service/ip-service.go @@ -285,6 +285,22 @@ func (r *ipResource) allocateIP(request *restful.Request, response *restful.Resp return } + if requestPayload.AddressFamily != nil { + ok := nw.AddressFamilies[metal.ToAddressFamily(string(*requestPayload.AddressFamily))] + if !ok { + r.sendError(request, response, httperrors.BadRequest( + fmt.Errorf("there is no prefix for the given addressfamily:%s present in this network:%s", string(*requestPayload.AddressFamily), requestPayload.NetworkID)), + ) + return + } + if specificIP != "" { + r.sendError(request, response, httperrors.BadRequest( + fmt.Errorf("it is not possible to specify specificIP and addressfamily"), + )) + return + } + } + p, err := r.mdc.Project().Get(request.Request.Context(), &mdmv1.ProjectGetRequest{Id: requestPayload.ProjectID}) if err != nil { r.sendError(request, response, defaultError(err)) @@ -320,7 +336,7 @@ func (r *ipResource) allocateIP(request *restful.Request, response *restful.Resp ctx := request.Request.Context() if specificIP == "" { - ipAddress, ipParentCidr, err = allocateRandomIP(ctx, nw, r.ipamer) + ipAddress, ipParentCidr, err = allocateRandomIP(ctx, nw, r.ipamer, requestPayload.AddressFamily) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -333,13 +349,13 @@ func (r *ipResource) allocateIP(request *restful.Request, response *restful.Resp } } - r.logger(request).Debug("allocated ip in ipam", "ip", ipAddress, "network", nw.ID) - ipType := metal.Ephemeral if requestPayload.Type == metal.Static { ipType = metal.Static } + r.logger(request).Info("allocated ip in ipam", "ip", ipAddress, "network", nw.ID, "type", ipType) + ip := &metal.IP{ IPAddress: ipAddress, ParentPrefixCidr: ipParentCidr, @@ -436,8 +452,24 @@ func allocateSpecificIP(ctx context.Context, parent *metal.Network, specificIP s return "", "", fmt.Errorf("specific ip not contained in any of the defined prefixes") } -func allocateRandomIP(ctx context.Context, parent *metal.Network, ipamer ipam.IPAMer) (ipAddress, parentPrefixCidr string, err error) { +func allocateRandomIP(ctx context.Context, parent *metal.Network, ipamer ipam.IPAMer, af *metal.AddressFamily) (ipAddress, parentPrefixCidr string, err error) { + var addressfamily = metal.IPv4AddressFamily + if af != nil { + addressfamily = *af + } + for _, prefix := range parent.Prefixes { + pfx, err := netip.ParsePrefix(prefix.String()) + if err != nil { + return "", "", fmt.Errorf("unable to parse prefix: %w", err) + } + if pfx.Addr().Is4() && addressfamily == metal.IPv6AddressFamily { + continue + } + if pfx.Addr().Is6() && addressfamily == metal.IPv4AddressFamily { + continue + } + ipAddress, err = ipamer.AllocateIP(ctx, prefix) if err != nil && errors.Is(err, goipam.ErrNoIPAvailable) { continue diff --git a/cmd/metal-api/internal/service/ip-service_test.go b/cmd/metal-api/internal/service/ip-service_test.go index 72889fd5a..a8af1a917 100644 --- a/cmd/metal-api/internal/service/ip-service_test.go +++ b/cmd/metal-api/internal/service/ip-service_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/metal-stack/metal-lib/bus" + "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/metal-lib/pkg/tag" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" @@ -285,6 +286,35 @@ func TestAllocateIP(t *testing.T) { wantedStatus: http.StatusUnprocessableEntity, wantErr: errors.New("specific ip not contained in any of the defined prefixes"), }, + { + name: "allocate a IPv4 address", + allocateRequest: v1.IPAllocateRequest{ + Describable: v1.Describable{}, + IPBase: v1.IPBase{ + ProjectID: "123", + NetworkID: testdata.NwIPAM.ID, + Type: metal.Ephemeral, + }, + AddressFamily: pointer.Pointer(metal.IPv4AddressFamily), + }, + wantedIP: "10.0.0.3", + wantedType: metal.Ephemeral, + wantedStatus: http.StatusCreated, + }, + { + name: "allocate a IPv6 address", + allocateRequest: v1.IPAllocateRequest{ + Describable: v1.Describable{}, + IPBase: v1.IPBase{ + ProjectID: "123", + NetworkID: testdata.NwIPAM.ID, + Type: metal.Ephemeral, + }, + AddressFamily: pointer.Pointer(metal.IPv6AddressFamily), + }, + wantedStatus: http.StatusBadRequest, + wantErr: errors.New("there is no prefix for the given addressfamily:IPv6 present in this network:4"), + }, } for i := range tests { tt := tests[i] @@ -313,6 +343,8 @@ func TestAllocateIP(t *testing.T) { err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) + require.NotNil(t, result.IPAddress) + require.NotNil(t, result.AllocationUUID) require.Equal(t, tt.wantedType, result.Type) require.Equal(t, tt.wantedIP, result.IPAddress) require.Equal(t, tt.name, *result.Name) diff --git a/cmd/metal-api/internal/service/machine-service.go b/cmd/metal-api/internal/service/machine-service.go index a95112188..d432a216f 100644 --- a/cmd/metal-api/internal/service/machine-service.go +++ b/cmd/metal-api/internal/service/machine-service.go @@ -1575,13 +1575,13 @@ func gatherNetworksFromSpec(ds *datastore.RethinkStore, allocationSpec *machineA network.ips = append(network.ips, *ip) } - for _, pn := range privateNetworks { - if pn.network.PartitionID != partitionPrivateSuperNetwork.PartitionID { - return nil, fmt.Errorf("private network %q must be located in the partition where the machine is going to be placed", pn.network.ID) + for _, privateNetwork := range privateNetworks { + if privateNetwork.network.PartitionID != partitionPrivateSuperNetwork.PartitionID { + return nil, fmt.Errorf("private network %q must be located in the partition where the machine is going to be placed", privateNetwork.network.ID) } - if !pn.auto && len(pn.ips) == 0 { - return nil, fmt.Errorf("the private network %q has no auto ip acquisition, but no suitable IPs were provided, which would lead into a machine having no ip address", pn.network.ID) + if !privateNetwork.auto && len(privateNetwork.ips) == 0 { + return nil, fmt.Errorf("the private network %q has no auto ip acquisition, but no suitable IPs were provided, which would lead into a machine having no ip address", privateNetwork.network.ID) } } @@ -1612,25 +1612,29 @@ func gatherUnderlayNetwork(ds *datastore.RethinkStore, partition *metal.Partitio func makeMachineNetwork(ctx context.Context, ds *datastore.RethinkStore, ipamer ipam.IPAMer, allocationSpec *machineAllocationSpec, n *allocationNetwork) (*metal.MachineNetwork, error) { if n.auto { - ipAddress, ipParentCidr, err := allocateRandomIP(ctx, n.network, ipamer) - if err != nil { - return nil, fmt.Errorf("unable to allocate an ip in network: %s %w", n.network.ID, err) - } - ip := &metal.IP{ - IPAddress: ipAddress, - ParentPrefixCidr: ipParentCidr, - Name: allocationSpec.Name, - Description: "autoassigned", - NetworkID: n.network.ID, - Type: metal.Ephemeral, - ProjectID: allocationSpec.ProjectID, - } - ip.AddMachineId(allocationSpec.UUID) - err = ds.CreateIP(ip) - if err != nil { - return nil, err + + for af := range n.network.AddressFamilies { + addressFamily := metal.ToAddressFamily(string(af)) + ipAddress, ipParentCidr, err := allocateRandomIP(ctx, n.network, ipamer, &addressFamily) + if err != nil { + return nil, fmt.Errorf("unable to allocate an ip in network: %s %w", n.network.ID, err) + } + ip := &metal.IP{ + IPAddress: ipAddress, + ParentPrefixCidr: ipParentCidr, + Name: allocationSpec.Name, + Description: "autoassigned", + NetworkID: n.network.ID, + Type: metal.Ephemeral, + ProjectID: allocationSpec.ProjectID, + } + ip.AddMachineId(allocationSpec.UUID) + err = ds.CreateIP(ip) + if err != nil { + return nil, err + } + n.ips = append(n.ips, *ip) } - n.ips = append(n.ips, *ip) } // from the makeNetworks call, a lot of ips might be set in this network diff --git a/cmd/metal-api/internal/service/machine-service_allocation_test.go b/cmd/metal-api/internal/service/machine-service_allocation_test.go index 1cb50dce7..bc42120ec 100644 --- a/cmd/metal-api/internal/service/machine-service_allocation_test.go +++ b/cmd/metal-api/internal/service/machine-service_allocation_test.go @@ -380,7 +380,7 @@ func createTestdata(machineCount int, rs *datastore.RethinkStore, ipamer ipam.IP private, err := ipamer.AllocateChildPrefix(ctx, tenantSuper, 22) require.NoError(t, err) require.NotNil(t, private) - privateNetwork, err := metal.NewPrefixFromCIDR(private.String()) + privateNetwork, _, err := metal.NewPrefixFromCIDR(private.String()) require.NoError(t, err) require.NotNil(t, privateNetwork) diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index 8e0c4b41e..49e95bd4a 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -156,7 +156,11 @@ func (r *networkResource) findNetwork(request *restful.Request, response *restfu return } ctx := request.Request.Context() - usage := getNetworkUsage(ctx, nw, r.ipamer) + usage, err := getNetworkUsage(ctx, nw, r.ipamer) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } r.send(request, response, http.StatusOK, v1.NewNetworkResponse(nw, usage)) } @@ -170,7 +174,12 @@ func (r *networkResource) listNetworks(request *restful.Request, response *restf ctx := request.Request.Context() var result []*v1.NetworkResponse for i := range nws { - usage := getNetworkUsage(ctx, &nws[i], r.ipamer) + usage, err := getNetworkUsage(ctx, &nws[i], r.ipamer) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + result = append(result, v1.NewNetworkResponse(&nws[i], usage)) } @@ -200,7 +209,12 @@ func (r *networkResource) findNetworks(request *restful.Request, response *restf ctx := request.Request.Context() result := []*v1.NetworkResponse{} for i := range nws { - usage := getNetworkUsage(ctx, &nws[i], r.ipamer) + usage, err := getNetworkUsage(ctx, &nws[i], r.ipamer) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + result = append(result, v1.NewNetworkResponse(&nws[i], usage)) } @@ -263,35 +277,17 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } - // all Prefixes must be valid and from the same addressfamily - prefixes, addressFamily, err := validatePrefixes(requestPayload.Prefixes) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return + var childPrefixLength = metal.ChildPrefixLength{} + for af, length := range requestPayload.DefaultChildPrefixLength { + childPrefixLength[metal.ToAddressFamily(string(af))] = length } - // all DestinationPrefixes must be valid and from the same addressfamily - _, _, err = validatePrefixes(requestPayload.DestinationPrefixes) + + prefixes, destPrefixes, addressFamilies, err := validatePrefixesAndAddressFamilies(requestPayload.Prefixes, requestPayload.DestinationPrefixes, childPrefixLength, privateSuper) if err != nil { r.sendError(request, response, httperrors.BadRequest(err)) return } - if privateSuper && requestPayload.DefaultChildPrefixLength == nil { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("private super network must always contain a defaultchildprefixlength"))) - return - } - - destPrefixes := metal.Prefixes{} - for i := range requestPayload.DestinationPrefixes { - p := requestPayload.DestinationPrefixes[i] - prefix, err := metal.NewPrefixFromCIDR(p) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) - return - } - destPrefixes = append(destPrefixes, *prefix) - } - allNws, err := r.ds.ListNetworks() if err != nil { r.sendError(request, response, defaultError(err)) @@ -338,9 +334,8 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest if privateSuper { var nw metal.Network err := r.ds.FindNetwork(&datastore.NetworkSearchQuery{ - PartitionID: &partition.ID, - PrivateSuper: pointer.Pointer(true), - AddressFamily: pointer.Pointer(metal.ToAddressFamily(string(*addressFamily))), + PartitionID: &partition.ID, + PrivateSuper: pointer.Pointer(true), }, &nw) r.log.Info("createnetwork", "found", nw) if err != nil && !metal.IsNotFound(err) { @@ -348,7 +343,7 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } if nw.ID != "" { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("partition with id %q already has a private super network for addressfamily:%s", partition.ID, *addressFamily))) + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("partition with id %q already has a private super network", partition.ID))) return } } @@ -393,33 +388,17 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest Name: name, Description: description, }, - Prefixes: prefixes, - DestinationPrefixes: destPrefixes, - PartitionID: partitionID, - ProjectID: projectID, - Nat: nat, - PrivateSuper: privateSuper, - Underlay: underlay, - Vrf: vrf, - Labels: labels, - AddressFamily: metal.AddressFamily(*addressFamily), - } - - // check if childprefixlength is set and matches addressfamily - if requestPayload.DefaultChildPrefixLength != nil && privateSuper { - dcpl := *requestPayload.DefaultChildPrefixLength - for _, p := range prefixes { - ipprefix, err := netip.ParsePrefix(p.String()) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) - return - } - if dcpl <= uint8(ipprefix.Bits()) { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given defaultchildprefixlength %d is not greater than prefix length of:%s", dcpl, p.String()))) - return - } - } - nw.DefaultChildPrefixLength = requestPayload.DefaultChildPrefixLength + Prefixes: prefixes, + DestinationPrefixes: destPrefixes, + DefaultChildPrefixLength: childPrefixLength, + PartitionID: partitionID, + ProjectID: projectID, + Nat: nat, + PrivateSuper: privateSuper, + Underlay: underlay, + Vrf: vrf, + Labels: labels, + AddressFamilies: addressFamilies, } ctx := request.Request.Context() @@ -437,40 +416,86 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } - usage := getNetworkUsage(ctx, nw, r.ipamer) + usage, err := getNetworkUsage(ctx, nw, r.ipamer) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage)) } -func validatePrefixes(prefixes []string) (metal.Prefixes, *v1.AddressFamily, error) { +func validatePrefixesAndAddressFamilies(prefixes, destinationPrefixes []string, defaultChildPrefixLength metal.ChildPrefixLength, privateSuper bool) (metal.Prefixes, metal.Prefixes, metal.AddressFamilies, error) { + + pfxs, addressFamilies, err := validatePrefixes(prefixes) + if err != nil { + return nil, nil, nil, err + } + // all DestinationPrefixes must be valid and from the same addressfamily + destPfxs, destinationAddressFamilies, err := validatePrefixes(destinationPrefixes) + if err != nil { + return nil, nil, nil, err + } + if len(destinationAddressFamilies) > len(addressFamilies) { + return nil, nil, nil, fmt.Errorf("destination prefixes have more addressfamilies then prefixes") + + } + + if !privateSuper { + return pfxs, destPfxs, addressFamilies, nil + } + + if len(defaultChildPrefixLength) == 0 { + return nil, nil, nil, fmt.Errorf("private super network must always contain a defaultchildprefixlength") + } + + for af, length := range defaultChildPrefixLength { + fmt.Printf("af %s length:%d addressfamilies:%v", af, length, addressFamilies) + ok := addressFamilies[af] + if !ok { + return nil, nil, nil, fmt.Errorf("private super network must always contain a defaultchildprefixlength per addressfamily:%s", af) + } + + // check if childprefixlength is set and matches addressfamily + for _, p := range pfxs { + ipprefix, err := netip.ParsePrefix(p.String()) + if err != nil { + return nil, nil, nil, fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err) + } + if ipprefix.Addr().Is4() && af == metal.IPv6AddressFamily { + continue + } + if ipprefix.Addr().Is6() && af == metal.IPv4AddressFamily { + continue + } + if length <= uint8(ipprefix.Bits()) { + return nil, nil, nil, fmt.Errorf("given defaultchildprefixlength %d is not greater than prefix length of:%s", length, p.String()) + } + } + } + + return pfxs, destPfxs, addressFamilies, nil +} + +func validatePrefixes(prefixes []string) (metal.Prefixes, metal.AddressFamilies, error) { var ( result metal.Prefixes - addressFamilies = make(map[string]bool) - addressFamily v1.AddressFamily + addressFamilies = metal.AddressFamilies{} ) for _, p := range prefixes { - prefix, err := metal.NewPrefixFromCIDR(p) - if err != nil { - return nil, nil, fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err) - } - ipprefix, err := netip.ParsePrefix(p) + prefix, ipprefix, err := metal.NewPrefixFromCIDR(p) if err != nil { return nil, nil, fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err) } if ipprefix.Addr().Is4() { - addressFamilies["ipv4"] = true - addressFamily = v1.IPv4AddressFamily + addressFamilies[metal.IPv4AddressFamily] = true } if ipprefix.Addr().Is6() { - addressFamilies["ipv6"] = true - addressFamily = v1.IPv6AddressFamily + addressFamilies[metal.IPv6AddressFamily] = true } result = append(result, *prefix) } - if len(addressFamilies) > 1 { - return nil, nil, fmt.Errorf("given prefixes have different addressfamilies") - } - return result, &addressFamily, nil + return result, addressFamilies, nil } // TODO add possibility to allocate from a non super network if given in the AllocateRequest and super has childprefixlength @@ -530,7 +555,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re destPrefixes := metal.Prefixes{} for _, p := range requestPayload.DestinationPrefixes { - prefix, err := metal.NewPrefixFromCIDR(p) + prefix, _, err := metal.NewPrefixFromCIDR(p) if err != nil { r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("given prefix %v is not a valid ip with mask: %w", p, err))) return @@ -539,20 +564,15 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re destPrefixes = append(destPrefixes, *prefix) } - addressFamily := v1.IPv4AddressFamily - if requestPayload.AddressFamily != nil { - addressFamily = v1.ToAddressFamily(*requestPayload.AddressFamily) - } - - r.log.Info("network allocate", "family", addressFamily, "partition", partition.ID) + r.log.Info("network allocate", "partition", partition.ID) var ( superNetwork metal.Network ) err = r.ds.FindNetwork(&datastore.NetworkSearchQuery{ - PartitionID: &partition.ID, - PrivateSuper: pointer.Pointer(true), - AddressFamily: pointer.Pointer(metal.ToAddressFamily(string(addressFamily))), + PartitionID: &partition.ID, + PrivateSuper: pointer.Pointer(true), + ParentNetworkID: requestPayload.ParentNetworkID, }, &superNetwork) if err != nil { r.sendError(request, response, defaultError(err)) @@ -560,7 +580,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re } if superNetwork.ID == "" { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("no supernetwork for addressfamily:%s found", addressFamily))) + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("no supernetwork found"))) return } if superNetwork.DefaultChildPrefixLength == nil { @@ -580,16 +600,30 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re DestinationPrefixes: destPrefixes, Shared: shared, Nat: nat, - AddressFamily: metal.AddressFamily(addressFamily), + AddressFamilies: superNetwork.AddressFamilies, } - // Allow configurable prefix length - length := *superNetwork.DefaultChildPrefixLength + // Allow configurable prefix length per AF + length := superNetwork.DefaultChildPrefixLength if requestPayload.Length != nil { - // requestPayload.Length must be smaller than defaultchildprefixlength, but is checked in go-ipam - length = *requestPayload.Length + for af, l := range requestPayload.Length { + length[metal.ToAddressFamily(string(af))] = l + } + } + + if requestPayload.AddressFamily != nil { + af := metal.ToAddressFamily(string(*requestPayload.AddressFamily)) + bits, ok := length[af] + if !ok { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("addressfamiliy %s specified, but no childprefixlength for this addressfamily", *requestPayload.AddressFamily))) + return + } + length = metal.ChildPrefixLength{ + af: bits, + } } - r.log.Info("network allocate", "supernetwork", superNetwork.ID, "defaultchildprefixlength", *superNetwork.DefaultChildPrefixLength, "length", length) + + 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) @@ -598,24 +632,31 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re return } - usage := getNetworkUsage(ctx, nw, r.ipamer) + usage, err := getNetworkUsage(ctx, nw, r.ipamer) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } 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, childLength uint8) (*metal.Network, error) { +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) if err != nil { return nil, fmt.Errorf("could not acquire a vrf: %w", err) } - childPrefix, err := createChildPrefix(ctx, parent.Prefixes, childLength, ipamer) - if err != nil { - return nil, err - } - - if childPrefix == nil { - return nil, fmt.Errorf("could not allocate child prefix in parent network: %s", parent.ID) + var childPrefixes = metal.Prefixes{} + for af, childLength := range childLengths { + childPrefix, err := createChildPrefix(ctx, parent.Prefixes, childLength, ipamer) + 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) + } + childPrefixes = append(childPrefixes, *childPrefix) } nw := &metal.Network{ @@ -623,7 +664,7 @@ func createChildNetwork(ctx context.Context, ds *datastore.RethinkStore, ipamer Name: nwSpec.Name, Description: nwSpec.Description, }, - Prefixes: metal.Prefixes{*childPrefix}, + Prefixes: childPrefixes, DestinationPrefixes: nwSpec.DestinationPrefixes, PartitionID: parent.PartitionID, ProjectID: nwSpec.ProjectID, @@ -634,6 +675,7 @@ func createChildNetwork(ctx context.Context, ds *datastore.RethinkStore, ipamer Vrf: *vrf, ParentNetworkID: parent.ID, Labels: nwSpec.Labels, + AddressFamilies: nwSpec.AddressFamilies, } err = ds.CreateNetwork(nw) @@ -719,18 +761,11 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest ) if len(requestPayload.Prefixes) > 0 { - // all Prefixes must be valid and from the same addressfamily - prefixes, af, err := validatePrefixes(requestPayload.Prefixes) + prefixes, _, err := validatePrefixes(requestPayload.Prefixes) if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return - } - - if *af != v1.FromAddressFamily(oldNetwork.AddressFamily) { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("new prefixes have different addressfamily %q then existing prefixes %q", *af, oldNetwork.AddressFamily))) + r.sendError(request, response, defaultError(err)) return } - newNetwork.Prefixes = prefixes prefixesToBeRemoved = oldNetwork.SubtractPrefixes(newNetwork.Prefixes...) @@ -751,6 +786,22 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest prefixesToBeAdded = newNetwork.SubtractPrefixes(oldNetwork.Prefixes...) } + if len(requestPayload.DestinationPrefixes) > 0 { + destPrefixes, _, err := validatePrefixes(requestPayload.DestinationPrefixes) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + newNetwork.DestinationPrefixes = destPrefixes + } + + _, _, addressFamilies, err := validatePrefixesAndAddressFamilies(newNetwork.Prefixes.String(), newNetwork.DestinationPrefixes.String(), oldNetwork.DefaultChildPrefixLength, oldNetwork.PrivateSuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + newNetwork.AddressFamilies = addressFamilies + ctx := request.Request.Context() for _, p := range prefixesToBeRemoved { @@ -769,29 +820,17 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest } } - if len(requestPayload.DestinationPrefixes) > 0 { - // all Prefixes must be valid and from the same addressfamily - prefixes, af, err := validatePrefixes(requestPayload.Prefixes) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return - } - - if *af != v1.FromAddressFamily(oldNetwork.AddressFamily) { - r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("new destination prefixes have different addressfamily %q then existing destination prefixes %q", *af, oldNetwork.AddressFamily))) - return - } - - newNetwork.DestinationPrefixes = prefixes - } - err = r.ds.UpdateNetwork(oldNetwork, &newNetwork) if err != nil { r.sendError(request, response, defaultError(err)) return } - usage := getNetworkUsage(ctx, &newNetwork, r.ipamer) + usage, err := getNetworkUsage(ctx, &newNetwork, r.ipamer) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } r.send(request, response, http.StatusOK, v1.NewNetworkResponse(&newNetwork, usage)) } @@ -855,23 +894,35 @@ func (r *networkResource) deleteNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusOK, v1.NewNetworkResponse(nw, &metal.NetworkUsage{})) } -func getNetworkUsage(ctx context.Context, nw *metal.Network, ipamer ipam.IPAMer) *metal.NetworkUsage { - usage := &metal.NetworkUsage{} +func getNetworkUsage(ctx context.Context, nw *metal.Network, ipamer ipam.IPAMer) (*metal.NetworkUsage, error) { + usage := &metal.NetworkUsage{ + AvailableIPs: make(map[metal.AddressFamily]uint64), + UsedIPs: make(map[metal.AddressFamily]uint64), + AvailablePrefixes: make(map[metal.AddressFamily]uint64), + UsedPrefixes: make(map[metal.AddressFamily]uint64), + } if nw == nil { - return usage + return usage, nil } for _, prefix := range nw.Prefixes { + pfx, err := netip.ParsePrefix(prefix.String()) + if err != nil { + return nil, err + } + key := metal.IPv4AddressFamily + if pfx.Addr().Is6() { + key = metal.IPv6AddressFamily + } u, err := ipamer.PrefixUsage(ctx, prefix.String()) if err != nil { - // FIXME: the error should not be swallowed here as this can return wrong usage information to the clients - continue + return nil, err } - usage.AvailableIPs = usage.AvailableIPs + u.AvailableIPs - usage.UsedIPs = usage.UsedIPs + u.UsedIPs - usage.AvailablePrefixes = usage.AvailablePrefixes + u.AvailablePrefixes - usage.UsedPrefixes = usage.UsedPrefixes + u.UsedPrefixes + usage.AvailableIPs[key] += u.AvailableIPs[key] + usage.UsedIPs[key] += u.UsedIPs[key] + usage.AvailablePrefixes[key] += u.AvailablePrefixes[key] + usage.UsedPrefixes[key] += u.UsedPrefixes[key] } - return usage + return usage, nil } func createChildPrefix(ctx context.Context, parentPrefixes metal.Prefixes, childLength uint8, ipamer ipam.IPAMer) (*metal.Prefix, error) { diff --git a/cmd/metal-api/internal/service/network-service_test.go b/cmd/metal-api/internal/service/network-service_test.go index ed400b38d..2be66e90e 100644 --- a/cmd/metal-api/internal/service/network-service_test.go +++ b/cmd/metal-api/internal/service/network-service_test.go @@ -7,11 +7,10 @@ import ( "log/slog" "net/http" "net/http/httptest" - "net/netip" - "reflect" "testing" restful "github.com/emicklei/go-restful/v3" + "github.com/google/go-cmp/cmp" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" mdmv1mock "github.com/metal-stack/masterdata-api/api/v1/mocks" mdm "github.com/metal-stack/masterdata-api/pkg/client" @@ -21,7 +20,6 @@ import ( v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" "github.com/metal-stack/metal-lib/httperrors" - "github.com/metal-stack/metal-lib/pkg/pointer" testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" r "gopkg.in/rethinkdb/rethinkdb-go.v6" @@ -268,7 +266,7 @@ func Test_networkResource_createNetwork(t *testing.T) { prefixes []string destinationPrefixes []string vrf uint - childprefixlength *uint8 + childprefixlength metal.ChildPrefixLength privateSuper bool underlay bool nat bool @@ -286,17 +284,19 @@ func Test_networkResource_createNetwork(t *testing.T) { expectedStatus: http.StatusCreated, }, { - name: "privatesuper IPv4", - networkName: testdata.Nw1.Name, - partitionID: testdata.Nw1.PartitionID, - projectID: testdata.Nw1.ProjectID, - prefixes: []string{"172.0.0.0/24"}, - destinationPrefixes: []string{"0.0.0.0/0"}, - childprefixlength: pointer.Pointer(uint8(22)), + name: "privatesuper IPv4", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"172.0.0.0/24"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + childprefixlength: metal.ChildPrefixLength{ + metal.IPv4AddressFamily: 22, + }, privateSuper: true, vrf: uint(10000), expectedStatus: http.StatusBadRequest, - expectedErrorMessage: "partition with id \"1\" already has a private super network for addressfamily:IPv4", + expectedErrorMessage: "given defaultchildprefixlength 22 is not greater than prefix length of:172.0.0.0/24", }, { name: "privatesuper IPv4 without defaultchildprefixlength", @@ -311,25 +311,30 @@ func Test_networkResource_createNetwork(t *testing.T) { expectedErrorMessage: "private super network must always contain a defaultchildprefixlength", }, { - name: "privatesuper IPv6", + name: "privatesuper Mixed", + networkName: "privatesuper mixed", + partitionID: "3", + projectID: "", + prefixes: []string{"fdaa:bbcc::/50", "172.0.0.0/16"}, + destinationPrefixes: []string{"::/0", "0.0.0.0/0"}, + childprefixlength: metal.ChildPrefixLength{ + metal.IPv4AddressFamily: 22, + metal.IPv6AddressFamily: 64, + }, + privateSuper: true, + vrf: uint(10000), + expectedStatus: http.StatusCreated, + }, + { + name: "broken IPv4", networkName: testdata.Nw1.Name, partitionID: testdata.Nw1.PartitionID, projectID: testdata.Nw1.ProjectID, - prefixes: []string{"fdaa:bbcc::/50"}, - destinationPrefixes: []string{"::/0"}, - childprefixlength: pointer.Pointer(uint8(64)), - privateSuper: true, - vrf: uint(10000), - expectedStatus: http.StatusCreated, - }, - { - name: "broken IPv4", - networkName: testdata.Nw1.Name, - partitionID: testdata.Nw1.PartitionID, - projectID: testdata.Nw1.ProjectID, - prefixes: []string{"192.168.265.0/24"}, - destinationPrefixes: []string{"0.0.0.0/0"}, - childprefixlength: pointer.Pointer(uint8(64)), + prefixes: []string{"192.168.265.0/24"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + childprefixlength: metal.ChildPrefixLength{ + metal.IPv6AddressFamily: 64, + }, privateSuper: true, vrf: uint(10000), expectedStatus: http.StatusBadRequest, @@ -348,15 +353,14 @@ func Test_networkResource_createNetwork(t *testing.T) { expectedErrorMessage: "given prefix fdaa:::/50 is not a valid ip with mask: netip.ParsePrefix(\"fdaa:::/50\"): ParseAddr(\"fdaa:::\"): each colon-separated field must have at least one digit (at \":\")", }, { - name: "mixed prefix addressfamilies", - networkName: testdata.Nw1.Name, - partitionID: testdata.Nw1.PartitionID, - projectID: testdata.Nw1.ProjectID, - prefixes: []string{"172.0.0.0/24", "fdaa:bbcc::/50"}, - destinationPrefixes: []string{"0.0.0.0/0"}, - vrf: uint(10000), - expectedStatus: http.StatusBadRequest, - expectedErrorMessage: "given prefixes have different addressfamilies", + name: "mixed prefix addressfamilies", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"172.0.0.0/24", "fdaa:bbcc::/50"}, + destinationPrefixes: []string{"0.0.0.0/0"}, + vrf: uint(10000), + expectedStatus: http.StatusCreated, }, { name: "broken destinationprefix", @@ -370,12 +374,14 @@ func Test_networkResource_createNetwork(t *testing.T) { expectedErrorMessage: "given prefix 0.0.0.0/33 is not a valid ip with mask: netip.ParsePrefix(\"0.0.0.0/33\"): prefix length out of range", }, { - name: "broken childprefixlength", - networkName: testdata.Nw1.Name, - partitionID: testdata.Nw1.PartitionID, - projectID: testdata.Nw1.ProjectID, - prefixes: []string{"fdaa:bbcc::/50"}, - childprefixlength: pointer.Pointer(uint8(50)), + name: "broken childprefixlength", + networkName: testdata.Nw1.Name, + partitionID: testdata.Nw1.PartitionID, + projectID: testdata.Nw1.ProjectID, + prefixes: []string{"fdaa:bbcc::/50"}, + childprefixlength: metal.ChildPrefixLength{ + metal.IPv6AddressFamily: 50, + }, privateSuper: true, vrf: uint(10000), expectedStatus: http.StatusBadRequest, @@ -445,8 +451,7 @@ func Test_networkResource_allocateNetwork(t *testing.T) { networkName string partitionID string projectID string - childprefixlength *uint8 - addressFamily *string + childprefixlength metal.ChildPrefixLength shared bool expectedStatus int expectedErrorMessage string @@ -459,29 +464,32 @@ func Test_networkResource_allocateNetwork(t *testing.T) { expectedStatus: http.StatusCreated, }, { - name: "simple ipv4, specific childprefixlength", - networkName: "tenantv4.2", - partitionID: testdata.Partition2.ID, - projectID: "project-1", - childprefixlength: pointer.Pointer(uint8(29)), - expectedStatus: http.StatusCreated, + name: "simple ipv4, specific childprefixlength", + networkName: "tenantv4.2", + partitionID: testdata.Partition2.ID, + projectID: "project-1", + childprefixlength: metal.ChildPrefixLength{ + metal.IPv4AddressFamily: 29, + }, + expectedStatus: http.StatusCreated, }, { name: "ipv6 default childprefixlength", networkName: "tenantv6", partitionID: testdata.Partition2.ID, projectID: "project-1", - addressFamily: pointer.Pointer("ipv6"), expectedStatus: http.StatusCreated, }, { - name: "simple ipv6, specific childprefixlength", - networkName: "tenantv6.2", - partitionID: testdata.Partition2.ID, - projectID: "project-1", - addressFamily: pointer.Pointer("ipv6"), - childprefixlength: pointer.Pointer(uint8(58)), - expectedStatus: http.StatusCreated, + name: "mixed, specific childprefixlength", + networkName: "tenantv6.2", + partitionID: "4", + projectID: "project-1", + childprefixlength: metal.ChildPrefixLength{ + metal.IPv4AddressFamily: 22, + metal.IPv6AddressFamily: 58, + }, + expectedStatus: http.StatusCreated, }, } for _, tt := range tests { @@ -509,10 +517,9 @@ func Test_networkResource_allocateNetwork(t *testing.T) { container := restful.NewContainer().Add(networkservice) allocateRequest := &v1.NetworkAllocateRequest{ - Describable: v1.Describable{Name: &tt.networkName}, - NetworkBase: v1.NetworkBase{PartitionID: &tt.partitionID, ProjectID: &tt.projectID}, - AddressFamily: tt.addressFamily, - Length: tt.childprefixlength, + Describable: v1.Describable{Name: &tt.networkName}, + NetworkBase: v1.NetworkBase{PartitionID: &tt.partitionID, ProjectID: &tt.projectID}, + Length: tt.childprefixlength, } js, err := json.Marshal(allocateRequest) @@ -538,23 +545,12 @@ func Test_networkResource_allocateNetwork(t *testing.T) { var result v1.NetworkResponse err = json.NewDecoder(resp.Body).Decode(&result) - requestAF := "ipv4" - if tt.addressFamily != nil { - requestAF = "ipv6" - } - require.GreaterOrEqual(t, len(result.Prefixes), 1) - resultFirstPrefix := netip.MustParsePrefix(result.Prefixes[0]) - af := "ipv4" - if resultFirstPrefix.Addr().Is6() { - af = "ipv6" - } require.NoError(t, err) require.Equal(t, tt.networkName, *result.Name) require.Equal(t, tt.partitionID, *result.PartitionID) require.Equal(t, tt.projectID, *result.ProjectID) - require.Equal(t, requestAF, af) } } } @@ -564,41 +560,41 @@ func Test_validatePrefixes(t *testing.T) { name string prefixes []string wantPrefixes metal.Prefixes - wantAF *v1.AddressFamily + wantAF metal.AddressFamilies wantErr bool }{ { name: "simple all ipv4", prefixes: []string{"10.0.0.0/8", "11.0.0.0/24"}, wantPrefixes: metal.Prefixes{{IP: "10.0.0.0", Length: "8"}, {IP: "11.0.0.0", Length: "24"}}, - wantAF: pointer.Pointer(v1.IPv4AddressFamily), + wantAF: metal.AddressFamilies{metal.IPv4AddressFamily: true}, }, { name: "simple all ipv6", prefixes: []string{"2001::/64", "fbaa::/48"}, wantPrefixes: metal.Prefixes{{IP: "2001::", Length: "64"}, {IP: "fbaa::", Length: "48"}}, - wantAF: pointer.Pointer(v1.IPv6AddressFamily), + wantAF: metal.AddressFamilies{metal.IPv6AddressFamily: true}, }, { name: "mixed af", prefixes: []string{"10.0.0.0/8", "2001::/64"}, - wantPrefixes: nil, - wantAF: nil, - wantErr: true, + wantPrefixes: metal.Prefixes{{IP: "10.0.0.0", Length: "8"}, {IP: "2001::", Length: "64"}}, + wantAF: metal.AddressFamilies{metal.IPv4AddressFamily: true, metal.IPv6AddressFamily: true}, + wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, got1, err := validatePrefixes(tt.prefixes) + got, af, err := validatePrefixes(tt.prefixes) if (err != nil) != tt.wantErr { t.Errorf("validatePrefixes() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.wantPrefixes) { - t.Errorf("validatePrefixes() got = %v, want %v", got, tt.wantPrefixes) + if diff := cmp.Diff(got, tt.wantPrefixes); diff != "" { + t.Errorf("validatePrefixes() diff=%s", diff) } - if !reflect.DeepEqual(got1, tt.wantAF) { - t.Errorf("validatePrefixes() got1 = %v, want %v", got1, tt.wantAF) + if diff := cmp.Diff(af, tt.wantAF); diff != "" { + t.Errorf("validatePrefixes() diff=%s", diff) } }) } diff --git a/cmd/metal-api/internal/service/v1/ip.go b/cmd/metal-api/internal/service/v1/ip.go index a09292670..13b8a011f 100644 --- a/cmd/metal-api/internal/service/v1/ip.go +++ b/cmd/metal-api/internal/service/v1/ip.go @@ -20,7 +20,8 @@ type IPIdentifiable struct { type IPAllocateRequest struct { Describable IPBase - MachineID *string `json:"machineid" description:"the machine id this ip should be associated with" optional:"true"` + MachineID *string `json:"machineid" description:"the machine id this ip should be associated with" optional:"true"` + AddressFamily *metal.AddressFamily `json:"addressfamily" description:"the addressfamily to allocate a ip address from the given network, defaults to IPv4" enum:"IPv4|IPv6"` } type IPUpdateRequest struct { diff --git a/cmd/metal-api/internal/service/v1/network.go b/cmd/metal-api/internal/service/v1/network.go index c6ebb28b6..fd854531b 100644 --- a/cmd/metal-api/internal/service/v1/network.go +++ b/cmd/metal-api/internal/service/v1/network.go @@ -15,24 +15,25 @@ type NetworkBase struct { // NetworkImmutable defines the properties which are immutable in the Network. type NetworkImmutable struct { - Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` - DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` - DefaultChildPrefixLength *uint8 `json:"defaultchildprefixlength" description:"if privatesuper, this defines the bitlen of child prefixes if not nil" optional:"true"` - Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` - PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` - Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` - Vrf *uint `json:"vrf" description:"the vrf this network is associated with" optional:"true"` - VrfShared *bool `json:"vrfshared" description:"if set to true, given vrf can be used by multiple networks, which is sometimes useful for network partitioning (default: false)" optional:"true"` - ParentNetworkID *string `json:"parentnetworkid" description:"the id of the parent network" optional:"true"` - AddressFamily AddressFamily `json:"addressfamily" description:"the addressfamily either IPv4 or IPv6 of this network" enum:"IPv4|IPv6"` + Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` + DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` + DefaultChildPrefixLength metal.ChildPrefixLength `json:"defaultchildprefixlength" description:"if privatesuper, this defines the bitlen of child prefixes per addressfamily if not nil" optional:"true"` + // FIXME add IPv6Nat + Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` + PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` + Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` + Vrf *uint `json:"vrf" description:"the vrf this network is associated with" optional:"true"` + VrfShared *bool `json:"vrfshared" description:"if set to true, given vrf can be used by multiple networks, which is sometimes useful for network partitioning (default: false)" optional:"true"` + ParentNetworkID *string `json:"parentnetworkid" description:"the id of the parent network" optional:"true"` + AddressFamilies metal.AddressFamilies `json:"addressfamilies" description:"the addressfamilies in this network, either IPv4 or IPv6 or both"` } // NetworkUsage reports core metrics about available and used IPs or Prefixes in a Network. type NetworkUsage struct { - 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"` + 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"` } // NetworkCreateRequest is used to create a new Network. @@ -47,42 +48,11 @@ type NetworkCreateRequest struct { type NetworkAllocateRequest struct { Describable NetworkBase - DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` - Nat *bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip" optional:"true"` - AddressFamily *string `json:"address_family" description:"can be ipv4 or ipv6, defaults to ipv4"` - Length *uint8 `json:"length" description:"the bitlen of the prefix to allocate, defaults to defaultchildprefixlength of super prefix"` -} - -// 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 -} - -// FromAddressFamily will convert from a metal.AddressFamily to a AddressFamily -func FromAddressFamily(af metal.AddressFamily) AddressFamily { - switch af { - case metal.IPv4AddressFamily: - return IPv4AddressFamily - case metal.IPv6AddressFamily: - return IPv6AddressFamily - } - return IPv4AddressFamily + DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` + Nat *bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip" optional:"true"` + Length metal.ChildPrefixLength `json:"length" description:"the bitlen of the prefix to allocate, defaults to defaultchildprefixlength of super prefix"` + ParentNetworkID *string `json:"parentnetworkid" description:"the parent network from which this network should be allocated"` + AddressFamily *metal.AddressFamily `json:"addressfamily" description:"the addressfamily to allocate a child network defaults. If not specified, the child network inherits the addressfamilies from the parent." enum:"IPv4|IPv6"` } // NetworkFindRequest is used to find a Network with different criteria. @@ -114,7 +84,10 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw return nil } - var parentNetworkID *string + var ( + parentNetworkID *string + ) + if network.ParentNetworkID != "" { parentNetworkID = &network.ParentNetworkID } @@ -148,7 +121,7 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw Underlay: network.Underlay, Vrf: &network.Vrf, ParentNetworkID: parentNetworkID, - AddressFamily: ToAddressFamily(string(network.AddressFamily)), + AddressFamilies: network.AddressFamilies, }, Usage: NetworkUsage{ AvailableIPs: usage.AvailableIPs, diff --git a/cmd/metal-api/internal/testdata/ipam.go b/cmd/metal-api/internal/testdata/ipam.go index ca7823197..a06b786c1 100644 --- a/cmd/metal-api/internal/testdata/ipam.go +++ b/cmd/metal-api/internal/testdata/ipam.go @@ -36,7 +36,8 @@ func InitMockIpamData(dbMock *r.Mock, withIP bool) (ipam.IPAMer, error) { Name: "IPAM Network", Description: "description IPAM", }, - Prefixes: prefixesIPAM, + Prefixes: prefixesIPAM, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true}, } // now, let's get an ip from the IPAM for IPAMIP diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index f3ea6c97e..b96d76c68 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -4,7 +4,6 @@ import ( "errors" "time" - "github.com/metal-stack/metal-lib/pkg/pointer" "github.com/metal-stack/metal-lib/pkg/tag" "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" @@ -286,8 +285,8 @@ var ( prefixIPAM = metal.Prefix{IP: "10.0.0.0", Length: "16"} superPrefix = metal.Prefix{IP: "10.1.0.0", Length: "16"} superPrefixV6 = metal.Prefix{IP: "2001::", Length: "48"} - cpl1 = uint8(28) - cpl2 = uint8(22) + cpl1 = metal.ChildPrefixLength{metal.IPv4AddressFamily: 28} + cpl2 = metal.ChildPrefixLength{metal.IPv4AddressFamily: 22} prefixes1 = []metal.Prefix{prefix1, prefix2} prefixes2 = []metal.Prefix{prefix2} @@ -303,8 +302,8 @@ var ( PartitionID: Partition1.ID, Prefixes: prefixes1, PrivateSuper: true, - DefaultChildPrefixLength: &cpl1, - AddressFamily: metal.IPv4AddressFamily, + DefaultChildPrefixLength: cpl1, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true}, } Nw2 = metal.Network{ Base: metal.Base{ @@ -315,8 +314,8 @@ var ( PartitionID: Partition1.ID, Prefixes: prefixes2, Underlay: true, - DefaultChildPrefixLength: &cpl2, - AddressFamily: metal.IPv4AddressFamily, + DefaultChildPrefixLength: cpl2, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true}, } Nw3 = metal.Network{ Base: metal.Base{ @@ -327,7 +326,7 @@ var ( Prefixes: prefixes3, PartitionID: Partition1.ID, ParentNetworkID: Nw1.ID, - AddressFamily: metal.IPv4AddressFamily, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true}, } Partition1PrivateSuperNetwork = metal.Network{ @@ -336,7 +335,7 @@ var ( }, Prefixes: metal.Prefixes{superPrefix}, PartitionID: Partition1.ID, - DefaultChildPrefixLength: pointer.Pointer(uint8(22)), + DefaultChildPrefixLength: metal.ChildPrefixLength{metal.IPv4AddressFamily: 22}, ParentNetworkID: "", ProjectID: "", PrivateSuper: true, @@ -350,8 +349,8 @@ var ( }, Prefixes: metal.Prefixes{superPrefix}, PartitionID: Partition2.ID, - DefaultChildPrefixLength: pointer.Pointer(uint8(22)), - AddressFamily: metal.IPv4AddressFamily, + DefaultChildPrefixLength: metal.ChildPrefixLength{metal.IPv4AddressFamily: 22}, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true}, ParentNetworkID: "", ProjectID: "", PrivateSuper: true, @@ -365,8 +364,23 @@ var ( }, Prefixes: metal.Prefixes{superPrefixV6}, PartitionID: Partition2.ID, - DefaultChildPrefixLength: pointer.Pointer(uint8(64)), - AddressFamily: metal.IPv6AddressFamily, + DefaultChildPrefixLength: metal.ChildPrefixLength{metal.IPv6AddressFamily: 64}, + AddressFamilies: metal.AddressFamilies{metal.IPv6AddressFamily: true}, + ParentNetworkID: "", + ProjectID: "", + PrivateSuper: true, + Nat: false, + Underlay: false, + } + + Partition4PrivateSuperNetworkMixed = metal.Network{ + Base: metal.Base{ + ID: "super-tenant-network-2-mixed", + }, + Prefixes: metal.Prefixes{superPrefix, superPrefixV6}, + PartitionID: "4", + DefaultChildPrefixLength: metal.ChildPrefixLength{metal.IPv4AddressFamily: 22, metal.IPv6AddressFamily: 64}, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true, metal.IPv6AddressFamily: true}, ParentNetworkID: "", ProjectID: "", PrivateSuper: true, @@ -484,7 +498,8 @@ var ( Name: "IPAM Network", Description: "description IPAM", }, - Prefixes: prefixesIPAM, + Prefixes: prefixesIPAM, + AddressFamilies: metal.AddressFamilies{metal.IPv4AddressFamily: true}, } // IPs @@ -492,21 +507,21 @@ var ( IPAddress: "1.2.3.4", Name: "Image 1", Description: "description 1", - Type: "ephemeral", + Type: metal.Ephemeral, ProjectID: "1", } IP2 = metal.IP{ IPAddress: "2.3.4.5", Name: "Image 2", Description: "description 2", - Type: "static", + Type: metal.Static, ProjectID: "1", } IP3 = metal.IP{ IPAddress: "3.4.5.6", Name: "Image 3", Description: "description 3", - Type: "static", + Type: metal.Static, Tags: []string{tag.MachineID}, ProjectID: "1", } @@ -514,7 +529,7 @@ var ( IPAddress: "2001:0db8:85a3::1", Name: "IPv6 4", Description: "description 4", - Type: "ephemeral", + Type: metal.Ephemeral, ProjectID: "1", } IPAMIP = metal.IP{ @@ -565,7 +580,13 @@ var ( Description: "description 3", }, } - + Partition4 = metal.Partition{ + Base: metal.Base{ + ID: "4", + Name: "partition4", + Description: "description 4", + }, + } // Switches Switch1 = metal.Switch{ Base: metal.Base{ @@ -841,6 +862,8 @@ func InitMockDBData(mock *r.Mock) { mock.On(r.DB("mockdb").Table("partition").Get("1")).Return(Partition1, nil) mock.On(r.DB("mockdb").Table("partition").Get("2")).Return(Partition2, nil) mock.On(r.DB("mockdb").Table("partition").Get("3")).Return(Partition3, nil) + mock.On(r.DB("mockdb").Table("partition").Get("4")).Return(Partition4, nil) + mock.On(r.DB("mockdb").Table("partition").Get("404")).Return(nil, errors.New("Test Error")) mock.On(r.DB("mockdb").Table("partition").Get("999")).Return(nil, nil) mock.On(r.DB("mockdb").Table("image").Get("image-1")).Return(Img1, nil) @@ -867,20 +890,16 @@ func InitMockDBData(mock *r.Mock) { mock.On(r.DB("mockdb").Table("network").Get("999")).Return(nil, nil) mock.On(r.DB("mockdb").Table("network").Filter( func(var_3 r.Term) r.Term { return var_3.Field("partitionid").Eq("1") }).Filter( - func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) }).Filter( - func(var_5 r.Term) r.Term { return var_5.Field("addressfamily").Eq("IPv4") })).Return(Nw3, nil) - mock.On(r.DB("mockdb").Table("network").Filter( - func(var_3 r.Term) r.Term { return var_3.Field("partitionid").Eq("1") }).Filter( - func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) }).Filter( - func(var_5 r.Term) r.Term { return var_5.Field("addressfamily").Eq("IPv6") })).Return(nil, metal.NotFound("network not found")) + func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) })).Return(Nw3, nil) mock.On(r.DB("mockdb").Table("network").Filter( func(var_3 r.Term) r.Term { return var_3.Field("partitionid").Eq("2") }).Filter( - func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) }).Filter( - func(var_5 r.Term) r.Term { return var_5.Field("addressfamily").Eq("IPv4") })).Return(Partition2PrivateSuperNetwork, nil) + func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) })).Return(Partition2PrivateSuperNetwork, nil) mock.On(r.DB("mockdb").Table("network").Filter( - func(var_3 r.Term) r.Term { return var_3.Field("partitionid").Eq("2") }).Filter( - func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) }).Filter( - func(var_5 r.Term) r.Term { return var_5.Field("addressfamily").Eq("IPv6") })).Return(Partition2PrivateSuperNetworkV6, nil) + func(var_3 r.Term) r.Term { return var_3.Field("partitionid").Eq("3") }).Filter( + func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) })).Return(nil, nil) + mock.On(r.DB("mockdb").Table("network").Filter( + func(var_3 r.Term) r.Term { return var_3.Field("partitionid").Eq("4") }).Filter( + func(var_4 r.Term) r.Term { return var_4.Field("privatesuper").Eq(true) })).Return(Partition4PrivateSuperNetworkMixed, nil) mock.On(r.DB("mockdb").Table("ip").Get("1.2.3.4")).Return(IP1, nil) mock.On(r.DB("mockdb").Table("ip").Get("2.3.4.5")).Return(IP2, nil) diff --git a/spec/metal-api.json b/spec/metal-api.json index caa8a9b3b..2ebf1dbf8 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -255,9 +255,6 @@ }, "datastore.NetworkSearchQuery": { "properties": { - "addressfamily": { - "type": "string" - }, "destinationprefixes": { "items": { "type": "string" @@ -1510,6 +1507,14 @@ }, "v1.IPAllocateRequest": { "properties": { + "addressfamily": { + "description": "the addressfamily to allocate a ip address from the given network, defaults to IPv4", + "enum": [ + "IPv4", + "IPv6" + ], + "type": "string" + }, "description": { "description": "a description for this entity", "type": "string" @@ -1547,6 +1552,7 @@ } }, "required": [ + "addressfamily", "networkid", "projectid", "type" @@ -3571,8 +3577,12 @@ }, "v1.NetworkAllocateRequest": { "properties": { - "address_family": { - "description": "can be ipv4 or ipv6, defaults to ipv4", + "addressfamily": { + "description": "the addressfamily to allocate a child network defaults. If not specified, the child network inherits the addressfamilies from the parent.", + "enum": [ + "IPv4", + "IPv6" + ], "type": "string" }, "description": { @@ -3594,9 +3604,11 @@ "type": "object" }, "length": { + "additionalProperties": { + "type": "integer" + }, "description": "the bitlen of the prefix to allocate, defaults to defaultchildprefixlength of super prefix", - "format": "byte", - "type": "integer" + "type": "object" }, "name": { "description": "a readable name for this entity", @@ -3606,6 +3618,10 @@ "description": "if set to true, packets leaving this network get masqueraded behind interface ip", "type": "boolean" }, + "parentnetworkid": { + "description": "the parent network from which this network should be allocated", + "type": "string" + }, "partitionid": { "description": "the partition this network belongs to", "type": "string" @@ -3620,8 +3636,9 @@ } }, "required": [ - "address_family", - "length" + "addressfamily", + "length", + "parentnetworkid" ] }, "v1.NetworkBase": { @@ -3650,17 +3667,18 @@ "v1.NetworkCreateRequest": { "properties": { "addressfamily": { - "description": "the addressfamily either IPv4 or IPv6 of this network", - "enum": [ - "IPv4", - "IPv6" - ], - "type": "string" + "additionalProperties": { + "type": "boolean" + }, + "description": "the addressfamilies in this network, either IPv4 or IPv6 or both", + "type": "object" }, "defaultchildprefixlength": { - "description": "if privatesuper, this defines the bitlen of child prefixes if not nil", - "format": "byte", - "type": "integer" + "additionalProperties": { + "type": "integer" + }, + "description": "if privatesuper, this defines the bitlen of child prefixes per addressfamily if not nil", + "type": "object" }, "description": { "description": "a description for this entity", @@ -3745,9 +3763,6 @@ }, "v1.NetworkFindRequest": { "properties": { - "addressfamily": { - "type": "string" - }, "destinationprefixes": { "items": { "type": "string" @@ -3800,17 +3815,18 @@ "description": "a network which contains prefixes from which IP addresses can be allocated\nprefixes that are reachable within this network", "properties": { "addressfamily": { - "description": "the addressfamily either IPv4 or IPv6 of this network", - "enum": [ - "IPv4", - "IPv6" - ], - "type": "string" + "additionalProperties": { + "type": "boolean" + }, + "description": "the addressfamilies in this network, either IPv4 or IPv6 or both", + "type": "object" }, "defaultchildprefixlength": { - "description": "if privatesuper, this defines the bitlen of child prefixes if not nil", - "format": "byte", - "type": "integer" + "additionalProperties": { + "type": "integer" + }, + "description": "if privatesuper, this defines the bitlen of child prefixes per addressfamily if not nil", + "type": "object" }, "destinationprefixes": { "description": "the destination prefixes of this network", @@ -3864,12 +3880,11 @@ "v1.NetworkResponse": { "properties": { "addressfamily": { - "description": "the addressfamily either IPv4 or IPv6 of this network", - "enum": [ - "IPv4", - "IPv6" - ], - "type": "string" + "additionalProperties": { + "type": "boolean" + }, + "description": "the addressfamilies in this network, either IPv4 or IPv6 or both", + "type": "object" }, "changed": { "description": "the last changed timestamp of this entity", @@ -3884,9 +3899,11 @@ "type": "string" }, "defaultchildprefixlength": { - "description": "if privatesuper, this defines the bitlen of child prefixes if not nil", - "format": "byte", - "type": "integer" + "additionalProperties": { + "type": "integer" + }, + "description": "if privatesuper, this defines the bitlen of child prefixes per addressfamily if not nil", + "type": "object" }, "description": { "description": "a description for this entity", From 6430ea1766c4c75c1f9776525001cf3358a95d77 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 14 Aug 2024 12:48:51 +0200 Subject: [PATCH 06/12] Make additional announcable cidrs configurable per tenant super network --- .../06_additional_announcable_cidrs.go | 40 +++++++++++ .../datastore/network_integration_test.go | 3 + cmd/metal-api/internal/metal/network.go | 23 ++++--- .../internal/service/network-service.go | 50 +++++++++++--- .../internal/service/switch-service.go | 67 ++++++++++++------- .../internal/service/switch-service_test.go | 50 +++++++++----- cmd/metal-api/internal/service/v1/network.go | 41 ++++++------ cmd/metal-api/internal/testdata/testdata.go | 15 +++-- go.mod | 17 ++--- go.sum | 32 +++++---- spec/metal-api.json | 28 ++++++++ 11 files changed, 256 insertions(+), 110 deletions(-) create mode 100644 cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go diff --git a/cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go b/cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go new file mode 100644 index 000000000..0ddc3bb5b --- /dev/null +++ b/cmd/metal-api/internal/datastore/migrations/06_additional_announcable_cidrs.go @@ -0,0 +1,40 @@ +package migrations + +import ( + r "gopkg.in/rethinkdb/rethinkdb-go.v6" + + "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" +) + +func init() { + datastore.MustRegisterMigration(datastore.Migration{ + Name: "migrate super tenant networks to contain additionannouncablecidrs", + 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 { + if !old.PrivateSuper { + continue + } + new := old + + if len(old.AdditionalAnnouncableCIDRs) == 0 { + new.AdditionalAnnouncableCIDRs = []string{ + // This was the previous hard coded default in metal-core + "10.240.0.0/12", + } + } + + err = rs.UpdateNetwork(&old, &new) + if err != nil { + return err + } + } + return nil + }, + }) +} diff --git a/cmd/metal-api/internal/datastore/network_integration_test.go b/cmd/metal-api/internal/datastore/network_integration_test.go index a756df908..782612997 100644 --- a/cmd/metal-api/internal/datastore/network_integration_test.go +++ b/cmd/metal-api/internal/datastore/network_integration_test.go @@ -65,6 +65,9 @@ func (_ *networkTestable) defaultBody(n *metal.Network) *metal.Network { if n.DestinationPrefixes == nil { n.DestinationPrefixes = metal.Prefixes{} } + if n.AdditionalAnnouncableCIDRs == nil { + n.AdditionalAnnouncableCIDRs = []string{} + } return n } diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 9f2477d71..22d344bfc 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -207,17 +207,18 @@ 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"` + 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"` + AdditionalAnnouncableCIDRs []string `rethinkdb:"additionalannouncablecidrs" json:"additionalannouncablecidrs" description:"list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork"` } // Networks is a list of networks. diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index bd4db1d64..9203742d6 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -6,6 +6,7 @@ import ( "fmt" "log/slog" "net/http" + "net/netip" mdmv1 "github.com/metal-stack/masterdata-api/api/v1" mdm "github.com/metal-stack/masterdata-api/pkg/client" @@ -358,6 +359,12 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest return } + additionalAnnouncableCIDRs, err := validateAdditionalAnnouncableCIDRs(requestPayload.AdditionalAnnouncableCIDRs, privateSuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + if vrf != 0 { err = acquireVRF(r.ds, vrf) if err != nil { @@ -378,15 +385,16 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest Name: name, Description: description, }, - Prefixes: prefixes, - DestinationPrefixes: destPrefixes, - PartitionID: partitionID, - ProjectID: projectID, - Nat: nat, - PrivateSuper: privateSuper, - Underlay: underlay, - Vrf: vrf, - Labels: labels, + Prefixes: prefixes, + DestinationPrefixes: destPrefixes, + PartitionID: partitionID, + ProjectID: projectID, + Nat: nat, + PrivateSuper: privateSuper, + Underlay: underlay, + Vrf: vrf, + Labels: labels, + AdditionalAnnouncableCIDRs: additionalAnnouncableCIDRs, } ctx := request.Request.Context() @@ -409,6 +417,23 @@ func (r *networkResource) createNetwork(request *restful.Request, response *rest r.send(request, response, http.StatusCreated, v1.NewNetworkResponse(nw, usage)) } +func validateAdditionalAnnouncableCIDRs(additionalCidrs []string, privateSuper bool) ([]string, error) { + var result []string + if len(additionalCidrs) > 0 { + if !privateSuper { + return nil, errors.New("additionalannouncablecidrs can only be set in a private super network") + } + for _, cidr := range additionalCidrs { + _, err := netip.ParsePrefix(cidr) + if err != nil { + return nil, fmt.Errorf("given cidr:%q in additionalannouncablecidrs is malformed:%w", cidr, err) + } + result = append(result, cidr) + } + } + return result, nil +} + func (r *networkResource) allocateNetwork(request *restful.Request, response *restful.Response) { var requestPayload v1.NetworkAllocateRequest err := request.ReadEntity(&requestPayload) @@ -670,6 +695,13 @@ func (r *networkResource) updateNetwork(request *restful.Request, response *rest } } + additionalAnnouncableCIDRs, err := validateAdditionalAnnouncableCIDRs(requestPayload.AdditionalAnnouncableCIDRs, oldNetwork.PrivateSuper) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + newNetwork.AdditionalAnnouncableCIDRs = additionalAnnouncableCIDRs + err = r.ds.UpdateNetwork(oldNetwork, &newNetwork) if err != nil { r.sendError(request, response, defaultError(err)) diff --git a/cmd/metal-api/internal/service/switch-service.go b/cmd/metal-api/internal/service/switch-service.go index 8fea0f965..4309c2d61 100644 --- a/cmd/metal-api/internal/service/switch-service.go +++ b/cmd/metal-api/internal/service/switch-service.go @@ -144,7 +144,7 @@ func (r *switchResource) findSwitch(request *restful.Request, response *restful. return } - resp, err := makeSwitchResponse(s, r.ds) + resp, err := r.makeSwitchResponse(s) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -160,7 +160,7 @@ func (r *switchResource) listSwitches(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponseList(ss, r.ds) + resp, err := r.makeSwitchResponseList(ss) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -184,7 +184,7 @@ func (r *switchResource) findSwitches(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponseList(ss, r.ds) + resp, err := r.makeSwitchResponseList(ss) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -223,7 +223,7 @@ func (r *switchResource) deleteSwitch(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponse(s, r.ds) + resp, err := r.makeSwitchResponse(s) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -388,7 +388,7 @@ func (r *switchResource) toggleSwitchPort(request *restful.Request, response *re } } - resp, err := makeSwitchResponse(&newSwitch, r.ds) + resp, err := r.makeSwitchResponse(&newSwitch) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -437,7 +437,7 @@ func (r *switchResource) updateSwitch(request *restful.Request, response *restfu return } - resp, err := makeSwitchResponse(&newSwitch, r.ds) + resp, err := r.makeSwitchResponse(&newSwitch) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -553,7 +553,7 @@ func (r *switchResource) registerSwitch(request *restful.Request, response *rest } - resp, err := makeSwitchResponse(s, r.ds) + resp, err := r.makeSwitchResponse(s) if err != nil { r.sendError(request, response, defaultError(err)) return @@ -772,22 +772,22 @@ func updateSwitchNics(oldNics, newNics map[string]*metal.Nic, currentConnections return finalNics, nil } -func makeSwitchResponse(s *metal.Switch, ds *datastore.RethinkStore) (*v1.SwitchResponse, error) { - p, ips, machines, ss, err := findSwitchReferencedEntities(s, ds) +func (r *switchResource) makeSwitchResponse(s *metal.Switch) (*v1.SwitchResponse, error) { + p, ips, machines, ss, err := findSwitchReferencedEntities(s, r.ds) if err != nil { return nil, err } - nics, err := makeSwitchNics(s, ips, machines) + nics, err := r.makeSwitchNics(s, ips, machines) if err != nil { return nil, err } - cons := makeSwitchCons(s) + cons := r.makeSwitchCons(s) return v1.NewSwitchResponse(s, ss, p, nics, cons), nil } -func makeBGPFilterFirewall(m metal.Machine) (v1.BGPFilter, error) { +func (r *switchResource) makeBGPFilterFirewall(m metal.Machine) (v1.BGPFilter, error) { vnis := []string{} cidrs := []string{} @@ -809,7 +809,7 @@ func makeBGPFilterFirewall(m metal.Machine) (v1.BGPFilter, error) { return v1.NewBGPFilter(vnis, cidrs), nil } -func makeBGPFilterMachine(m metal.Machine, ips metal.IPsMap) (v1.BGPFilter, error) { +func (r *switchResource) makeBGPFilterMachine(m metal.Machine, ips metal.IPsMap) (v1.BGPFilter, error) { vnis := []string{} cidrs := []string{} @@ -826,6 +826,23 @@ func makeBGPFilterMachine(m metal.Machine, ips metal.IPsMap) (v1.BGPFilter, erro // Allow all prefixes of the private network if private != nil { cidrs = append(cidrs, private.Prefixes...) + + privateNetwork, err := r.ds.FindNetworkByID(private.NetworkID) + if err != nil && !metal.IsNotFound(err) { + return v1.BGPFilter{}, err + } + if privateNetwork != nil { + parentNetwork, err := r.ds.FindNetworkByID(privateNetwork.ParentNetworkID) + if err != nil && !metal.IsNotFound(err) { + return v1.BGPFilter{}, err + } + // Only for private networks, AdditionalAnnouncableCIDRs are applied. + // they contain usually the pod- and service- cidrs in a kubernetes cluster + if parentNetwork != nil && len(parentNetwork.AdditionalAnnouncableCIDRs) > 0 { + r.log.Debug("makeBGPFilterMachine", "additional cidrs", parentNetwork.AdditionalAnnouncableCIDRs) + cidrs = append(cidrs, parentNetwork.AdditionalAnnouncableCIDRs...) + } + } } for _, i := range ips[m.Allocation.Project] { // No need to add /32 addresses of the primary network to the whitelist. @@ -884,7 +901,7 @@ func compactCidrs(cidrs []string) ([]string, error) { return compactedCidrs, nil } -func makeBGPFilter(m metal.Machine, vrf string, ips metal.IPsMap) (v1.BGPFilter, error) { +func (r *switchResource) makeBGPFilter(m metal.Machine, vrf string, ips metal.IPsMap) (v1.BGPFilter, error) { var ( filter v1.BGPFilter err error @@ -894,16 +911,16 @@ func makeBGPFilter(m metal.Machine, vrf string, ips metal.IPsMap) (v1.BGPFilter, // vrf "default" means: the firewall was successfully allocated and the switch port configured // otherwise the port is still not configured yet (pxe-setup) and a BGPFilter would break the install routine if vrf == "default" { - filter, err = makeBGPFilterFirewall(m) + filter, err = r.makeBGPFilterFirewall(m) } } else { - filter, err = makeBGPFilterMachine(m, ips) + filter, err = r.makeBGPFilterMachine(m, ips) } return filter, err } -func makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) (v1.SwitchNics, error) { +func (r *switchResource) makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) (v1.SwitchNics, error) { machinesByID := map[string]*metal.Machine{} for i, m := range machines { machinesByID[m.ID] = &machines[i] @@ -924,7 +941,7 @@ func makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) m := machinesBySwp[n.Name] var filter *v1.BGPFilter if m != nil && m.Allocation != nil { - f, err := makeBGPFilter(*m, n.Vrf, ips) + f, err := r.makeBGPFilter(*m, n.Vrf, ips) if err != nil { return nil, err } @@ -955,7 +972,7 @@ func makeSwitchNics(s *metal.Switch, ips metal.IPsMap, machines metal.Machines) return nics, nil } -func makeSwitchCons(s *metal.Switch) []v1.SwitchConnection { +func (r *switchResource) makeSwitchCons(s *metal.Switch) []v1.SwitchConnection { cons := []v1.SwitchConnection{} nicMap := s.Nics.ByName() @@ -1026,14 +1043,14 @@ func findSwitchReferencedEntities(s *metal.Switch, ds *datastore.RethinkStore) ( return p, ips.ByProjectID(), m, ss, nil } -func makeSwitchResponseList(ss metal.Switches, ds *datastore.RethinkStore) ([]*v1.SwitchResponse, error) { - pMap, ips, err := getSwitchReferencedEntityMaps(ds) +func (r *switchResource) makeSwitchResponseList(ss metal.Switches) ([]*v1.SwitchResponse, error) { + pMap, ips, err := getSwitchReferencedEntityMaps(r.ds) if err != nil { return nil, err } result := []*v1.SwitchResponse{} - m, err := ds.ListMachines() + m, err := r.ds.ListMachines() if err != nil { return nil, fmt.Errorf("could not find machines: %w", err) } @@ -1046,12 +1063,12 @@ func makeSwitchResponseList(ss metal.Switches, ds *datastore.RethinkStore) ([]*v p = &partitionEntity } - nics, err := makeSwitchNics(&sw, ips, m) + nics, err := r.makeSwitchNics(&sw, ips, m) if err != nil { return nil, err } - cons := makeSwitchCons(&sw) - ss, err := ds.GetSwitchStatus(sw.ID) + cons := r.makeSwitchCons(&sw) + ss, err := r.ds.GetSwitchStatus(sw.ID) if err != nil && !metal.IsNotFound(err) { return nil, err } diff --git a/cmd/metal-api/internal/service/switch-service_test.go b/cmd/metal-api/internal/service/switch-service_test.go index 8d003d12b..054770527 100644 --- a/cmd/metal-api/internal/service/switch-service_test.go +++ b/cmd/metal-api/internal/service/switch-service_test.go @@ -7,6 +7,7 @@ import ( "log/slog" "net/http" "net/http/httptest" + "os" "reflect" "testing" "time" @@ -403,7 +404,8 @@ func TestMakeBGPFilterFirewall(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - got, _ := makeBGPFilterFirewall(tt.args.machine) + r := switchResource{} + got, _ := r.makeBGPFilterFirewall(tt.args.machine) if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeBGPFilterFirewall() = %v, want %v", got, tt.want) } @@ -412,6 +414,8 @@ func TestMakeBGPFilterFirewall(t *testing.T) { } func TestMakeBGPFilterMachine(t *testing.T) { + ds, mock := datastore.InitMockDB(t) + type args struct { machine metal.Machine ipsMap metal.IPsMap @@ -446,29 +450,33 @@ func TestMakeBGPFilterMachine(t *testing.T) { Project: "project", MachineNetworks: []*metal.MachineNetwork{ { - IPs: []string{"10.1.0.1"}, - Prefixes: []string{"10.2.0.0/22", "10.1.0.0/22"}, - Vrf: 1234, - Private: true, + NetworkID: "1", + IPs: []string{"10.1.0.1"}, + Prefixes: []string{"10.2.0.0/22", "10.1.0.0/22"}, + Vrf: 1234, + Private: true, }, { - IPs: []string{"10.0.0.2", "10.0.0.1"}, - Vrf: 0, - Underlay: true, + NetworkID: "2", + IPs: []string{"10.0.0.2", "10.0.0.1"}, + Vrf: 0, + Underlay: true, }, { - IPs: []string{"212.89.42.2", "212.89.42.1"}, - Vrf: 104009, + NetworkID: "3", + IPs: []string{"212.89.42.2", "212.89.42.1"}, + Vrf: 104009, }, { - IPs: []string{"2001::"}, - Vrf: 104010, + NetworkID: "4", + IPs: []string{"2001::"}, + Vrf: 104010, }, }, }, }, }, - want: v1.NewBGPFilter([]string{}, []string{"10.1.0.0/22", "10.2.0.0/22", "100.127.1.1/32", "2001::1/128", "212.89.42.1/32", "212.89.42.2/32"}), + want: v1.NewBGPFilter([]string{}, []string{"10.1.0.0/22", "10.2.0.0/22", "100.127.1.1/32", "10.240.0.0/12", "2001::1/128", "212.89.42.1/32", "212.89.42.2/32"}), }, { name: "allow only allocated ips", @@ -483,8 +491,9 @@ func TestMakeBGPFilterMachine(t *testing.T) { Project: "project", MachineNetworks: []*metal.MachineNetwork{ { - IPs: []string{"212.89.42.2", "212.89.42.1"}, - Vrf: 104009, + NetworkID: "5", + IPs: []string{"212.89.42.2", "212.89.42.1"}, + Vrf: 104009, }, }, }, @@ -493,10 +502,16 @@ func TestMakeBGPFilterMachine(t *testing.T) { want: v1.NewBGPFilter([]string{}, []string{"212.89.42.1/32"}), }, } + for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - got, _ := makeBGPFilterMachine(tt.args.machine, tt.args.ipsMap) + mock.On(r.DB("mockdb").Table("network").Get(r.MockAnything())).Return(testdata.Partition1PrivateSuperNetwork, nil) + + r := switchResource{webResource: webResource{ds: ds, log: slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))}} + + got, _ := r.makeBGPFilterMachine(tt.args.machine, tt.args.ipsMap) + if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeBGPFilterMachine() = %v, want %v", got, tt.want) } @@ -603,7 +618,8 @@ func TestMakeSwitchNics(t *testing.T) { for i := range tests { tt := tests[i] t.Run(tt.name, func(t *testing.T) { - got, _ := makeSwitchNics(tt.args.s, tt.args.ips, tt.args.machines) + r := switchResource{} + got, _ := r.makeSwitchNics(tt.args.s, tt.args.ips, tt.args.machines) if !reflect.DeepEqual(got, tt.want) { t.Errorf("makeSwitchNics() = %v, want %v", got, tt.want) } diff --git a/cmd/metal-api/internal/service/v1/network.go b/cmd/metal-api/internal/service/v1/network.go index d1bb9cc73..911e3e540 100644 --- a/cmd/metal-api/internal/service/v1/network.go +++ b/cmd/metal-api/internal/service/v1/network.go @@ -15,14 +15,15 @@ type NetworkBase struct { // NetworkImmutable defines the properties which are immutable in the Network. type NetworkImmutable struct { - Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` - DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` - Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` - PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` - Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` - Vrf *uint `json:"vrf" description:"the vrf this network is associated with" optional:"true"` - VrfShared *bool `json:"vrfshared" description:"if set to true, given vrf can be used by multiple networks, which is sometimes useful for network partitioning (default: false)" optional:"true"` - ParentNetworkID *string `json:"parentnetworkid" description:"the id of the parent network" optional:"true"` + Prefixes []string `json:"prefixes" modelDescription:"a network which contains prefixes from which IP addresses can be allocated" description:"the prefixes of this network"` + DestinationPrefixes []string `json:"destinationprefixes" modelDescription:"prefixes that are reachable within this network" description:"the destination prefixes of this network"` + Nat bool `json:"nat" description:"if set to true, packets leaving this network get masqueraded behind interface ip"` + PrivateSuper bool `json:"privatesuper" description:"if set to true, this network will serve as a partition's super network for the internal machine networks,there can only be one privatesuper network per partition"` + Underlay bool `json:"underlay" description:"if set to true, this network can be used for underlay communication"` + Vrf *uint `json:"vrf" description:"the vrf this network is associated with" optional:"true"` + VrfShared *bool `json:"vrfshared" description:"if set to true, given vrf can be used by multiple networks, which is sometimes useful for network partitioning (default: false)" optional:"true"` + ParentNetworkID *string `json:"parentnetworkid" description:"the id of the parent network" optional:"true"` + AdditionalAnnouncableCIDRs []string `json:"additionalannouncablecidrs" description:"list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork" optional:"true"` } // NetworkUsage reports core metrics about available and used IPs or Prefixes in a Network. @@ -57,10 +58,11 @@ type NetworkFindRequest struct { // NetworkUpdateRequest defines the properties of a Network which can be updated. type NetworkUpdateRequest struct { Common - Prefixes []string `json:"prefixes" description:"the prefixes of this network" optional:"true"` - DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` - Labels map[string]string `json:"labels" description:"free labels that you associate with this network." optional:"true"` - Shared *bool `json:"shared" description:"marks a network as shareable." optional:"true"` + Prefixes []string `json:"prefixes" description:"the prefixes of this network" optional:"true"` + DestinationPrefixes []string `json:"destinationprefixes" description:"the destination prefixes of this network" optional:"true"` + Labels map[string]string `json:"labels" description:"free labels that you associate with this network." optional:"true"` + Shared *bool `json:"shared" description:"marks a network as shareable." optional:"true"` + AdditionalAnnouncableCIDRs []string `json:"additionalannouncablecidrs" description:"list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork" optional:"true"` } // NetworkResponse holds all properties returned in a FindNetwork or GetNetwork request. @@ -104,13 +106,14 @@ func NewNetworkResponse(network *metal.Network, usage *metal.NetworkUsage) *Netw Shared: &network.Shared, }, NetworkImmutable: NetworkImmutable{ - Prefixes: network.Prefixes.String(), - DestinationPrefixes: network.DestinationPrefixes.String(), - Nat: network.Nat, - PrivateSuper: network.PrivateSuper, - Underlay: network.Underlay, - Vrf: &network.Vrf, - ParentNetworkID: parentNetworkID, + Prefixes: network.Prefixes.String(), + DestinationPrefixes: network.DestinationPrefixes.String(), + Nat: network.Nat, + PrivateSuper: network.PrivateSuper, + Underlay: network.Underlay, + Vrf: &network.Vrf, + ParentNetworkID: parentNetworkID, + AdditionalAnnouncableCIDRs: network.AdditionalAnnouncableCIDRs, }, Usage: NetworkUsage{ AvailableIPs: usage.AvailableIPs, diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index 273f5bfb0..3002ac734 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -323,13 +323,14 @@ var ( Base: metal.Base{ ID: "super-tenant-network-1", }, - Prefixes: metal.Prefixes{{IP: "10.0.0.0", Length: "16"}}, - PartitionID: Partition1.ID, - ParentNetworkID: "", - ProjectID: "", - PrivateSuper: true, - Nat: false, - Underlay: false, + Prefixes: metal.Prefixes{{IP: "10.0.0.0", Length: "16"}}, + PartitionID: Partition1.ID, + ParentNetworkID: "", + ProjectID: "", + PrivateSuper: true, + Nat: false, + Underlay: false, + AdditionalAnnouncableCIDRs: []string{"10.240.0.0/12"}, } Partition2PrivateSuperNetwork = metal.Network{ diff --git a/go.mod b/go.mod index dfd9688d1..b738718f3 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( github.com/metal-stack/security v0.8.1 github.com/metal-stack/v v1.0.3 github.com/nsqio/go-nsq v1.1.0 - github.com/prometheus/client_golang v1.19.1 - github.com/samber/lo v1.46.0 + github.com/prometheus/client_golang v1.20.0 + github.com/samber/lo v1.47.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 @@ -75,7 +75,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v27.1.1+incompatible // indirect + github.com/docker/docker v27.1.2+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -94,7 +94,7 @@ require ( github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/gofrs/uuid/v5 v5.2.0 // indirect + github.com/gofrs/uuid/v5 v5.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -142,6 +142,7 @@ require ( github.com/moby/patternmatcher v0.6.0 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/user v0.2.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -200,15 +201,15 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.22.0 // indirect - golang.org/x/sys v0.23.0 // indirect + golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/time v0.6.0 // indirect; indirecct golang.zx2c4.com/wireguard/windows v0.5.3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect gopkg.in/cenkalti/backoff.v2 v2.2.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index f8cb449d6..1fb0b970e 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= +github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -161,8 +161,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= -github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= +github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk= +github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -256,6 +256,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= @@ -317,6 +319,8 @@ github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7z github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM= github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -366,8 +370,8 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -391,8 +395,8 @@ github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3 github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= -github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -580,8 +584,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -621,10 +625,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf h1:GillM0Ef0pkZPIB+5iO6SDK+4T9pf6TpaYR6ICD5rVE= -google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf h1:liao9UHurZLtiEwBgT9LMOnKYsHze6eA6w1KQCMVN2Q= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= +google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/spec/metal-api.json b/spec/metal-api.json index deac2690e..d05e69e14 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -3613,6 +3613,13 @@ }, "v1.NetworkCreateRequest": { "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "description": { "description": "a description for this entity", "type": "string" @@ -3746,6 +3753,13 @@ "v1.NetworkImmutable": { "description": "a network which contains prefixes from which IP addresses can be allocated\nprefixes that are reachable within this network", "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "destinationprefixes": { "description": "the destination prefixes of this network", "items": { @@ -3796,6 +3810,13 @@ }, "v1.NetworkResponse": { "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "changed": { "description": "the last changed timestamp of this entity", "format": "date-time", @@ -3895,6 +3916,13 @@ }, "v1.NetworkUpdateRequest": { "properties": { + "additionalannouncablecidrs": { + "description": "list of cidrs which are added to the route maps per tenant private network, these are typically pod- and service cidrs, can only be set in a supernetwork", + "items": { + "type": "string" + }, + "type": "array" + }, "description": { "description": "a description for this entity", "type": "string" From 72ac513f698090f143c60b2aaf84b4c1b41cfb8f Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 26 Aug 2024 10:38:42 +0200 Subject: [PATCH 07/12] satisfy linter --- cmd/metal-api/internal/service/network-service.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index 8683a3223..315e66e05 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -608,7 +608,7 @@ func (r *networkResource) allocateNetwork(request *restful.Request, response *re r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("no supernetwork found"))) return } - if superNetwork.DefaultChildPrefixLength == nil || len(superNetwork.DefaultChildPrefixLength) == 0 { + if len(superNetwork.DefaultChildPrefixLength) == 0 { r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("supernetwork %s has no defaultchildprefixlength specified", superNetwork.ID))) return } From d2cb195a4dfbbfe638987147e55cde752d5fa484 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 5 Sep 2024 14:40:01 +0200 Subject: [PATCH 08/12] sanitize go.mod --- go.mod | 9 ++------- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 0cbfd9362..c4b6f1925 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/metal-stack/metal-api go 1.23 -toolchain go1.23.0 - require ( connectrpc.com/connect v1.16.2 github.com/Masterminds/semver/v3 v3.3.0 @@ -40,11 +38,6 @@ require ( gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2 ) -require ( - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect -) - replace ( // Newer versions do not export base entities which are used to composite other entities. // This breaks metalctl and friends @@ -201,8 +194,10 @@ require ( go.mongodb.org/mongo-driver v1.16.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect go.opentelemetry.io/otel v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect go.opentelemetry.io/otel/metric v1.29.0 // indirect go.opentelemetry.io/otel/trace v1.29.0 + go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect diff --git a/go.sum b/go.sum index 8fbc7486d..15126f23e 100644 --- a/go.sum +++ b/go.sum @@ -479,8 +479,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+n go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= @@ -489,8 +489,8 @@ go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHy go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= From 44c8825d154f6b4d1280f3cec86261f6c7802831 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 2 Oct 2024 10:50:47 +0200 Subject: [PATCH 09/12] Remove false comment --- .../internal/datastore/migrations/08_childprefixlength.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/metal-api/internal/datastore/migrations/08_childprefixlength.go b/cmd/metal-api/internal/datastore/migrations/08_childprefixlength.go index b9d697c51..56103bf78 100644 --- a/cmd/metal-api/internal/datastore/migrations/08_childprefixlength.go +++ b/cmd/metal-api/internal/datastore/migrations/08_childprefixlength.go @@ -11,7 +11,6 @@ import ( func init() { type tmpPartition struct { - // In theory this might be set in a partition, but in reality its not set anywhere PrivateNetworkPrefixLength uint8 `rethinkdb:"privatenetworkprefixlength"` } datastore.MustRegisterMigration(datastore.Migration{ From 7568f8e7f306fee8e08e70bf4213427e66864642 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 7 Oct 2024 10:53:24 +0200 Subject: [PATCH 10/12] Updates --- go.mod | 33 ++++++++++++------------- go.sum | 76 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 55 insertions(+), 54 deletions(-) diff --git a/go.mod b/go.mod index 01a318242..81e697ef6 100644 --- a/go.mod +++ b/go.mod @@ -31,9 +31,9 @@ require ( github.com/stretchr/testify v1.9.0 github.com/testcontainers/testcontainers-go v0.33.0 go4.org/netipx v0.0.0-20231129151722-fdeea329fbba - golang.org/x/crypto v0.27.0 + golang.org/x/crypto v0.28.0 golang.org/x/sync v0.8.0 - google.golang.org/grpc v1.67.0 + google.golang.org/grpc v1.67.1 google.golang.org/protobuf v1.34.2 gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.2 ) @@ -80,7 +80,7 @@ require ( github.com/go-openapi/runtime v0.28.0 // indirect github.com/go-openapi/strfmt v0.23.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect @@ -93,8 +93,8 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect @@ -109,7 +109,7 @@ require ( github.com/lestrrat-go/jwx/v2 v2.1.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 // indirect + github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -138,7 +138,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/common v0.60.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/puzpuzpuz/xsync/v3 v3.4.0 // indirect github.com/redis/go-redis/v9 v9.6.1 // indirect @@ -157,7 +157,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect + github.com/tklauser/numcpus v0.9.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.56.0 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect @@ -168,23 +168,24 @@ require ( go.etcd.io/etcd/api/v3 v3.5.15 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect go.etcd.io/etcd/client/v3 v3.5.15 // indirect - go.mongodb.org/mongo-driver v1.17.0 // indirect + go.mongodb.org/mongo-driver v1.17.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect go.opentelemetry.io/otel v1.30.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 - golang.org/x/net v0.29.0 // indirect + golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 + golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f // indirect gopkg.in/cenkalti/backoff.v2 v2.2.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 0bf5c7c35..6366d6b4e 100644 --- a/go.sum +++ b/go.sum @@ -133,8 +133,8 @@ github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3Bum github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= -github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -175,10 +175,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -230,8 +230,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/looplab/fsm v1.0.2 h1:f0kdMzr4CRpXtaKKRUxwLYJ7PirTdwrtNumeLN+mDx8= github.com/looplab/fsm v1.0.2/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= -github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7 h1:5RK988zAqB3/AN3opGfRpoQgAVqr6/A5+qRTi67VUZY= -github.com/lufia/plan9stats v0.0.0-20240819163618-b1d8f4d146e7/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= +github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -321,8 +321,8 @@ github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zI github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= +github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= @@ -388,8 +388,8 @@ github.com/testcontainers/testcontainers-go v0.33.0 h1:zJS9PfXYT5O0ZFXM2xxXfk4J5 github.com/testcontainers/testcontainers-go v0.33.0/go.mod h1:W80YpTa8D5C3Yy16icheD01UTDu+LmXIA2Keo+jWtT8= github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= +github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.37.1-0.20220607072126-8a320890c08d/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= @@ -415,20 +415,20 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5 go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= -go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r40k= -go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= +go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= +go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -449,10 +449,10 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 h1:1wqE9dj9NpSm04INVsJhhEUzhuDVjbcyKH91sVyPATw= +golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= @@ -468,8 +468,8 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -500,19 +500,19 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -520,18 +520,18 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= -golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61 h1:pAjq8XSSzXoP9ya73v/w+9QEAAJNluLrpmMq5qFJQNY= -google.golang.org/genproto/googleapis/api v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:O6rP0uBq4k0mdi/b4ZEMAZjkhYWhS815kCvaMha4VN8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= +google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f h1:cUMEy+8oS78BWIH9OWazBkzbr090Od9tWBNtZHkOhf0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240930140551-af27646dc61f/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= From 2e72212a5a9159f12a01ff59ca9ba15977da8d1c Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Fri, 8 Nov 2024 16:22:17 +0100 Subject: [PATCH 11/12] Introduce a type --- cmd/metal-api/internal/ipam/ipam.go | 8 ++++---- cmd/metal-api/internal/metal/network.go | 9 +++++---- cmd/metal-api/internal/service/network-service.go | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cmd/metal-api/internal/ipam/ipam.go b/cmd/metal-api/internal/ipam/ipam.go index e65c0c59d..94a9d0bff 100644 --- a/cmd/metal-api/internal/ipam/ipam.go +++ b/cmd/metal-api/internal/ipam/ipam.go @@ -163,16 +163,16 @@ func (i *ipam) PrefixUsage(ctx context.Context, cidr string) (*metal.NetworkUsag if pfx.Addr().Is6() { af = metal.IPv6AddressFamily } - availableIPs := map[metal.AddressFamily]uint64{ + availableIPs := metal.AddressFamilyUsage{ af: usage.Msg.AvailableIps, } - usedIPs := map[metal.AddressFamily]uint64{ + usedIPs := metal.AddressFamilyUsage{ af: usage.Msg.AcquiredIps, } - availablePrefixes := map[metal.AddressFamily]uint64{ + availablePrefixes := metal.AddressFamilyUsage{ af: usage.Msg.AvailableSmallestPrefixes, } - usedPrefixes := map[metal.AddressFamily]uint64{ + usedPrefixes := metal.AddressFamilyUsage{ af: usage.Msg.AcquiredPrefixes, } return &metal.NetworkUsage{ diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 5a4f6e1ce..32a8ae222 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -228,6 +228,7 @@ type ChildPrefixLength map[AddressFamily]uint8 // AddressFamily identifies IPv4/IPv6 type AddressFamily string type AddressFamilies map[AddressFamily]bool +type AddressFamilyUsage map[AddressFamily]uint64 const ( // IPv4AddressFamily identifies IPv4 @@ -255,10 +256,10 @@ type NetworkMap map[string]Network // NetworkUsage contains usage information of a network type NetworkUsage struct { - AvailableIPs map[AddressFamily]uint64 `json:"available_ips" description:"the total available IPs" readonly:"true"` - UsedIPs map[AddressFamily]uint64 `json:"used_ips" description:"the total used IPs" readonly:"true"` - AvailablePrefixes map[AddressFamily]uint64 `json:"available_prefixes" description:"the total available 2 bit Prefixes" readonly:"true"` - UsedPrefixes map[AddressFamily]uint64 `json:"used_prefixes" description:"the total used Prefixes" readonly:"true"` + AvailableIPs AddressFamilyUsage `json:"available_ips" description:"the total available IPs" readonly:"true"` + UsedIPs AddressFamilyUsage `json:"used_ips" description:"the total used IPs" readonly:"true"` + AvailablePrefixes AddressFamilyUsage `json:"available_prefixes" description:"the total available 2 bit Prefixes" readonly:"true"` + UsedPrefixes AddressFamilyUsage `json:"used_prefixes" description:"the total used Prefixes" readonly:"true"` } // ByID creates an indexed map of networks where the id is the index. diff --git a/cmd/metal-api/internal/service/network-service.go b/cmd/metal-api/internal/service/network-service.go index 3e12f4760..7a9db59b8 100644 --- a/cmd/metal-api/internal/service/network-service.go +++ b/cmd/metal-api/internal/service/network-service.go @@ -960,10 +960,10 @@ func (r *networkResource) deleteNetwork(request *restful.Request, response *rest func (r *networkResource) getNetworkUsage(ctx context.Context, nw *metal.Network) (*metal.NetworkUsage, error) { usage := &metal.NetworkUsage{ - AvailableIPs: make(map[metal.AddressFamily]uint64), - UsedIPs: make(map[metal.AddressFamily]uint64), - AvailablePrefixes: make(map[metal.AddressFamily]uint64), - UsedPrefixes: make(map[metal.AddressFamily]uint64), + AvailableIPs: metal.AddressFamilyUsage{}, + UsedIPs: metal.AddressFamilyUsage{}, + AvailablePrefixes: metal.AddressFamilyUsage{}, + UsedPrefixes: metal.AddressFamilyUsage{}, } if nw == nil { return usage, nil From fbf197022f40d08030abe6f0bb34a3c126007fe7 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Wed, 13 Nov 2024 12:19:58 +0100 Subject: [PATCH 12/12] More consts --- cmd/metal-api/internal/metal/machine.go | 8 ++++---- cmd/metal-api/internal/metal/network.go | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cmd/metal-api/internal/metal/machine.go b/cmd/metal-api/internal/metal/machine.go index 9a52e2021..7f66632a1 100644 --- a/cmd/metal-api/internal/metal/machine.go +++ b/cmd/metal-api/internal/metal/machine.go @@ -284,17 +284,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" + newaf = IPv4AddressFamily } else if p.Addr().Is6() { - newaf = "ipv6" + newaf = IPv6AddressFamily } if af != "" && af != newaf { return fmt.Errorf("mixed address family in one rule is not supported:%v", cidrs) diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 32a8ae222..0cd638d47 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -4,6 +4,7 @@ import ( "net" "net/netip" "strconv" + "strings" "github.com/samber/lo" ) @@ -239,10 +240,10 @@ const ( // ToAddressFamily will convert a string af to a AddressFamily func ToAddressFamily(af string) AddressFamily { - switch af { - case "IPv4", "ipv4": + switch strings.ToLower(af) { + case "ipv4": return IPv4AddressFamily - case "IPv6", "ipv6": + case "ipv6": return IPv6AddressFamily } return IPv4AddressFamily