From 22d916e824fea5175a78453abb669d68e01ed1e0 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 4 Jul 2024 13:09:45 +0200 Subject: [PATCH] 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 a1a31357..8f8c506d 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 c57ee945..3f1bb977 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 5722e137..6548e05e 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,