Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add long awaited crud services for tenant #357

Merged
merged 13 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/metal-api/internal/service/partition-service.go
Original file line number Diff line number Diff line change
Expand Up @@ -382,8 +382,10 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque

partitionCapacities := []v1.PartitionCapacity{}
for _, p := range ps {
p := p
capacities := make(map[string]*v1.ServerCapacity)
for _, m := range machines {
m := m
if m.Partition == nil {
continue
}
Expand Down
5 changes: 2 additions & 3 deletions cmd/metal-api/internal/service/project-service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/metal-stack/security"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
"gopkg.in/rethinkdb/rethinkdb-go.v6"
r "gopkg.in/rethinkdb/rethinkdb-go.v6"
)

Expand Down Expand Up @@ -210,7 +209,7 @@ func Test_projectResource_deleteProject(t *testing.T) {
name string
userScenarios []security.User
projectServiceMock func(mock *mdmv1mock.ProjectServiceClient)
dsMock func(mock *rethinkdb.Mock)
dsMock func(mock *r.Mock)
id string
wantStatus int
want *v1.ProjectResponse
Expand Down Expand Up @@ -241,7 +240,7 @@ func Test_projectResource_deleteProject(t *testing.T) {
mock.On("Get", context.Background(), &mdmv1.ProjectGetRequest{Id: "123"}).Return(&mdmv1.ProjectResponse{Project: &mdmv1.Project{Meta: &mdmv1.Meta{Id: "123"}}}, nil)
mock.On("Delete", context.Background(), &mdmv1.ProjectDeleteRequest{Id: "123"}).Return(&mdmv1.ProjectResponse{Project: &mdmv1.Project{}}, nil)
},
dsMock: func(mock *rethinkdb.Mock) {
dsMock: func(mock *r.Mock) {
mock.On(r.DB("mockdb").Table("machine").Filter(r.MockAnything(), r.FilterOpts{})).Return([]metal.Machines{}, nil)
mock.On(r.DB("mockdb").Table("network").Filter(r.MockAnything(), r.FilterOpts{})).Return([]metal.Networks{}, nil)
mock.On(r.DB("mockdb").Table("ip").Filter(r.MockAnything(), r.FilterOpts{})).Return([]metal.IPs{}, nil)
Expand Down
170 changes: 167 additions & 3 deletions cmd/metal-api/internal/service/tenant-service.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package service

import (
"context"
"errors"
"net/http"

"github.com/metal-stack/masterdata-api/api/rest/mapper"
v1 "github.com/metal-stack/masterdata-api/api/rest/v1"
mdmv1 "github.com/metal-stack/masterdata-api/api/v1"
mdm "github.com/metal-stack/masterdata-api/pkg/client"
"go.uber.org/zap"
"google.golang.org/protobuf/types/known/wrapperspb"

restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/metal-stack/metal-lib/auditing"
"github.com/metal-stack/metal-lib/httperrors"
)

Expand Down Expand Up @@ -59,13 +61,54 @@ func (r *tenantResource) webService() *restful.WebService {
Returns(http.StatusOK, "OK", []v1.TenantResponse{}).
DefaultReturns("Error", httperrors.HTTPErrorResponse{}))

ws.Route(ws.POST("/find").
To(viewer(r.findTenants)).
Operation("findTenants").
Doc("get all tenants that match given properties").
Metadata(restfulspec.KeyOpenAPITags, tags).
Gerrit91 marked this conversation as resolved.
Show resolved Hide resolved
Metadata(auditing.Exclude, true).
Reads(v1.TenantFindRequest{}).
Writes([]v1.TenantResponse{}).
Returns(http.StatusOK, "OK", []v1.TenantResponse{}).
DefaultReturns("Error", httperrors.HTTPErrorResponse{}))

ws.Route(ws.DELETE("/{id}").
To(admin(r.deleteTenant)).
Operation("deleteTenant").
Doc("deletes a tenant and returns the deleted entity").
Param(ws.PathParameter("id", "identifier of the tenant").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(v1.TenantResponse{}).
Returns(http.StatusOK, "OK", v1.TenantResponse{}).
DefaultReturns("Error", httperrors.HTTPErrorResponse{}))

ws.Route(ws.PUT("/").
To(admin(r.createTenant)).
Operation("createTenant").
Doc("create a tenant. if the given ID already exists a conflict is returned").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(v1.TenantCreateRequest{}).
Returns(http.StatusCreated, "Created", v1.TenantResponse{}).
Returns(http.StatusConflict, "Conflict", httperrors.HTTPErrorResponse{}).
DefaultReturns("Error", httperrors.HTTPErrorResponse{}))

ws.Route(ws.POST("/").
To(admin(r.updateTenant)).
Operation("updateTenant").
Doc("update a tenant. optimistic lock error can occur.").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(v1.TenantUpdateRequest{}).
Returns(http.StatusOK, "Updated", v1.TenantResponse{}).
Returns(http.StatusPreconditionFailed, "OptimisticLock", httperrors.HTTPErrorResponse{}).
DefaultReturns("Error", httperrors.HTTPErrorResponse{}))

return ws
}

func (r *tenantResource) getTenant(request *restful.Request, response *restful.Response) {
id := request.PathParameter("id")

tres, err := r.mdc.Tenant().Get(context.Background(), &mdmv1.TenantGetRequest{Id: id})
tres, err := r.mdc.Tenant().Get(request.Request.Context(), &mdmv1.TenantGetRequest{Id: id})
if err != nil {
r.sendError(request, response, defaultError(err))
return
Expand All @@ -77,7 +120,7 @@ func (r *tenantResource) getTenant(request *restful.Request, response *restful.R
}

func (r *tenantResource) listTenants(request *restful.Request, response *restful.Response) {
tres, err := r.mdc.Tenant().Find(context.Background(), &mdmv1.TenantFindRequest{})
tres, err := r.mdc.Tenant().Find(request.Request.Context(), &mdmv1.TenantFindRequest{})
if err != nil {
r.sendError(request, response, defaultError(err))
return
Expand All @@ -91,3 +134,124 @@ func (r *tenantResource) listTenants(request *restful.Request, response *restful

r.send(request, response, http.StatusOK, v1ts)
}

func (r *tenantResource) findTenants(request *restful.Request, response *restful.Response) {
var requestPayload v1.TenantFindRequest
err := request.ReadEntity(&requestPayload)
if err != nil {
r.sendError(request, response, httperrors.BadRequest(err))
return
}

res, err := r.mdc.Tenant().Find(request.Request.Context(), mapper.ToMdmV1TenantFindRequest(&requestPayload))
if err != nil {
r.sendError(request, response, defaultError(err))
return
}

var ps []*v1.Tenant
for i := range res.Tenants {
v1p := mapper.ToV1Tenant(res.Tenants[i])
ps = append(ps, v1p)
}

r.send(request, response, http.StatusOK, ps)
}

func (r *tenantResource) createTenant(request *restful.Request, response *restful.Response) {
var pcr v1.TenantCreateRequest
err := request.ReadEntity(&pcr)
if err != nil {
r.sendError(request, response, httperrors.BadRequest(err))
return
}

tenant := mapper.ToMdmV1Tenant(&pcr.Tenant)

mdmv1pcr := &mdmv1.TenantCreateRequest{
Tenant: tenant,
}

p, err := r.mdc.Tenant().Create(request.Request.Context(), mdmv1pcr)
if err != nil {
r.sendError(request, response, defaultError(err))
return
}

v1p := mapper.ToV1Tenant(p.Tenant)
pcres := &v1.TenantResponse{
Tenant: *v1p,
}

r.send(request, response, http.StatusCreated, pcres)
}

func (r *tenantResource) deleteTenant(request *restful.Request, response *restful.Response) {
id := request.PathParameter("id")

pgr := &mdmv1.TenantGetRequest{
Id: id,
}
p, err := r.mdc.Tenant().Get(request.Request.Context(), pgr)
if err != nil {
r.sendError(request, response, defaultError(err))
return
}

plr, err := r.mdc.Project().Find(request.Request.Context(), &mdmv1.ProjectFindRequest{TenantId: wrapperspb.String(id)})
if err != nil {
r.sendError(request, response, defaultError(err))
return
}
if len(plr.Projects) > 0 {
r.sendError(request, response, httperrors.UnprocessableEntity(errors.New("there are still projects allocated by this tenant")))
return
}

pdr := &mdmv1.TenantDeleteRequest{
Id: p.Tenant.Meta.Id,
}
pdresponse, err := r.mdc.Tenant().Delete(request.Request.Context(), pdr)
if err != nil {
r.sendError(request, response, defaultError(err))
return
}

v1p := mapper.ToV1Tenant(pdresponse.Tenant)
pcres := &v1.TenantResponse{
Tenant: *v1p,
}

r.send(request, response, http.StatusOK, pcres)
}

func (r *tenantResource) updateTenant(request *restful.Request, response *restful.Response) {
var requestPayload v1.TenantUpdateRequest
err := request.ReadEntity(&requestPayload)
if err != nil {
r.sendError(request, response, httperrors.BadRequest(err))
return
}

if requestPayload.Tenant.Meta == nil {
r.sendError(request, response, httperrors.BadRequest(errors.New("tenant and tenant.meta must be specified")))
return
}

// new data
tenantUpdateData := mapper.ToMdmV1Tenant(&requestPayload.Tenant)

pur, err := r.mdc.Tenant().Update(request.Request.Context(), &mdmv1.TenantUpdateRequest{
Tenant: tenantUpdateData,
})
if err != nil {
r.sendError(request, response, defaultError(err))
return
}

v1p := mapper.ToV1Tenant(pur.Tenant)

r.send(request, response, http.StatusOK, &v1.TenantResponse{
Tenant: *v1p,
})
}
10 changes: 5 additions & 5 deletions cmd/metal-api/internal/service/tenant-service_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package service

import (
"context"
"fmt"
"testing"

Expand All @@ -12,6 +11,7 @@ import (
mdm "github.com/metal-stack/masterdata-api/pkg/client"
"github.com/metal-stack/metal-lib/httperrors"
"github.com/metal-stack/security"
testifymock "github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
Expand Down Expand Up @@ -66,7 +66,7 @@ func Test_tenantResource_getTenant(t *testing.T) {
userScenarios: []security.User{*testViewUser},
id: "122",
tenantServiceMock: func(mock *mdmv1mock.TenantServiceClient) {
mock.On("Get", context.Background(), &mdmv1.TenantGetRequest{Id: "122"}).Return(&mdmv1.TenantResponse{Tenant: &mdmv1.Tenant{Name: "t122"}}, nil)
mock.On("Get", testifymock.Anything, &mdmv1.TenantGetRequest{Id: "122"}).Return(&mdmv1.TenantResponse{Tenant: &mdmv1.Tenant{Name: "t122"}}, nil)
},
want: &v1.TenantResponse{Tenant: v1.Tenant{Name: "t122"}},
wantStatus: 200,
Expand All @@ -76,7 +76,7 @@ func Test_tenantResource_getTenant(t *testing.T) {
name: "entity allowed for user with admin privileges",
userScenarios: []security.User{*testAdminUser},
tenantServiceMock: func(mock *mdmv1mock.TenantServiceClient) {
mock.On("Get", context.Background(), &mdmv1.TenantGetRequest{Id: "123"}).Return(&mdmv1.TenantResponse{Tenant: &mdmv1.Tenant{Name: "t123"}}, nil)
mock.On("Get", testifymock.Anything, &mdmv1.TenantGetRequest{Id: "123"}).Return(&mdmv1.TenantResponse{Tenant: &mdmv1.Tenant{Name: "t123"}}, nil)
},
id: "123",
want: &v1.TenantResponse{Tenant: v1.Tenant{Name: "t123"}},
Expand Down Expand Up @@ -128,7 +128,7 @@ func Test_tenantResource_listTenants(t *testing.T) {
name: "entity allowed for user with view privileges",
userScenarios: []security.User{*testViewUser},
tenantServiceMock: func(mock *mdmv1mock.TenantServiceClient) {
mock.On("Find", context.Background(), &mdmv1.TenantFindRequest{}).Return(&mdmv1.TenantListResponse{Tenants: []*mdmv1.Tenant{{Name: "t121"}, {Name: "t122"}}}, nil)
mock.On("Find", testifymock.Anything, &mdmv1.TenantFindRequest{}).Return(&mdmv1.TenantListResponse{Tenants: []*mdmv1.Tenant{{Name: "t121"}, {Name: "t122"}}}, nil)
},
want: []*v1.Tenant{{Name: "t121"}, {Name: "t122"}},
wantStatus: 200,
Expand All @@ -138,7 +138,7 @@ func Test_tenantResource_listTenants(t *testing.T) {
name: "entity allowed for user with admin privileges",
userScenarios: []security.User{*testAdminUser},
tenantServiceMock: func(mock *mdmv1mock.TenantServiceClient) {
mock.On("Find", context.Background(), &mdmv1.TenantFindRequest{}).Return(&mdmv1.TenantListResponse{Tenants: []*mdmv1.Tenant{{Name: "t123"}}}, nil)
mock.On("Find", testifymock.Anything, &mdmv1.TenantFindRequest{}).Return(&mdmv1.TenantListResponse{Tenants: []*mdmv1.Tenant{{Name: "t123"}}}, nil)
},
want: []*v1.Tenant{{Name: "t123"}},
wantStatus: 200,
Expand Down
24 changes: 11 additions & 13 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@ require (
github.com/aws/aws-sdk-go v1.44.326
github.com/dustin/go-humanize v1.0.1
github.com/emicklei/go-restful-openapi/v2 v2.9.1
github.com/emicklei/go-restful/v3 v3.10.2
github.com/emicklei/go-restful/v3 v3.11.0
github.com/go-openapi/spec v0.20.9
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/google/uuid v1.3.1
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/juanfont/headscale v0.22.3
github.com/looplab/fsm v0.3.0
github.com/metal-stack/go-ipam v1.8.5
github.com/metal-stack/masterdata-api v0.9.0
github.com/metal-stack/metal-lib v0.13.1
github.com/metal-stack/security v0.6.6
github.com/metal-stack/masterdata-api v0.10.0
github.com/metal-stack/metal-lib v0.13.2
github.com/metal-stack/security v0.6.7
github.com/metal-stack/v v1.0.3
github.com/nsqio/go-nsq v1.1.0
github.com/prometheus/client_golang v1.16.0
Expand All @@ -37,8 +37,6 @@ require (
)

replace (
// Keep this because v3.10.x breaks image-create
github.com/emicklei/go-restful/v3 => github.com/emicklei/go-restful/v3 v3.9.0
// netipx and x/exp must be replaced for tailscale < 1.48
go4.org/netipx => go4.org/netipx v0.0.0-20230303233057-f1b76eb4bb35
golang.org/x/exp => golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
Expand All @@ -65,7 +63,7 @@ require (
github.com/coreos/go-oidc/v3 v3.6.0 // indirect
github.com/cpuguy83/dockercfg v0.3.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deckarep/golang-set/v2 v2.3.0 // indirect
github.com/deckarep/golang-set/v2 v2.3.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v24.0.5+incompatible // indirect
Expand Down Expand Up @@ -154,13 +152,13 @@ require (
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/stretchr/objx v0.5.1 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.48.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect
go.mongodb.org/mongo-driver v1.12.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go4.org/intern v0.0.0-20230205224052-192e9f60865c // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
Expand All @@ -176,9 +174,9 @@ require (
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 // indirect
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230726155614-23370e0ffb3e // indirect
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230726155614-23370e0ffb3e // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230815205213-6bfd019c3878 // 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
Expand Down
Loading