Skip to content

Commit

Permalink
add long awaited crud services for tenant (#357)
Browse files Browse the repository at this point in the history
  • Loading branch information
majst01 authored Sep 8, 2023
1 parent ebb12b0 commit 55a19ad
Show file tree
Hide file tree
Showing 7 changed files with 449 additions and 45 deletions.
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).
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

0 comments on commit 55a19ad

Please sign in to comment.