Skip to content

Commit

Permalink
feat: internal endpoint to delete all records related to a tenant
Browse files Browse the repository at this point in the history
Changelog: Title
Ticket: MEN-7317

Signed-off-by: Fabio Tranchitella <[email protected]>
  • Loading branch information
tranchitella committed Jul 3, 2024
1 parent e38b34c commit 8cfd4fe
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 0 deletions.
14 changes: 14 additions & 0 deletions api/http/internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,17 @@ func (h InternalController) sendMenderCommand(c *gin.Context, msgType string) {

c.JSON(http.StatusAccepted, nil)
}

func (h InternalController) DeleteTenant(c *gin.Context) {
ctx := c.Request.Context()
tenantID := c.Param("tenantId")

err := h.app.DeleteTenant(ctx, tenantID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
}

c.Status(http.StatusNoContent)
}
64 changes: 64 additions & 0 deletions api/http/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,67 @@ func TestInternalSendInventory(t *testing.T) {
})
}
}

func TestDeleteTenant(t *testing.T) {
t.Parallel()
const tenantID = "123456789012345678901234"

testCases := []struct {
Name string
Request *http.Request
Error error
Status int
}{{
Name: "ok",

Request: func() *http.Request {
repl := strings.NewReplacer(
":tenantId", tenantID,
)
req, _ := http.NewRequest("DELETE",
"http://localhost"+repl.Replace(APIURLInternalTenant),
nil,
)
return req
}(),

Status: http.StatusNoContent,
}, {
Name: "error, internal server error",

Request: func() *http.Request {
repl := strings.NewReplacer(
":tenantId", tenantID,
)
req, _ := http.NewRequest("DELETE",
"http://localhost"+repl.Replace(APIURLInternalTenant),
nil,
)
return req
}(),

Error: errors.New("error"),
Status: http.StatusInternalServerError,
}}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
app := &app_mocks.App{}
defer app.AssertExpectations(t)

router, _ := NewRouter(app, nil, nil)
s := httptest.NewServer(router)
defer s.Close()

app.On("DeleteTenant",
mock.MatchedBy(func(_ context.Context) bool {
return true
}),
tenantID,
).Return(tc.Error)

w := httptest.NewRecorder()
router.ServeHTTP(w, tc.Request)
assert.Equal(t, tc.Status, w.Code)
})
}
}
2 changes: 2 additions & 0 deletions api/http/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
APIURLInternalAlive = APIURLInternal + "/alive"
APIURLInternalHealth = APIURLInternal + "/health"
APIURLInternalShutdown = APIURLInternal + "/shutdown"
APIURLInternalTenant = APIURLInternal + "/tenants/:tenantId"
APIURLInternalDevices = APIURLInternal + "/tenants/:tenantId/devices"
APIURLInternalDevicesID = APIURLInternal +
"/tenants/:tenantId/devices/:deviceId"
Expand Down Expand Up @@ -89,6 +90,7 @@ func NewRouter(
router.GET(APIURLInternalShutdown, status.Shutdown)

internal := NewInternalController(app, natsClient)
router.DELETE(APIURLInternalTenant, internal.DeleteTenant)
router.POST(APIURLInternalDevicesIDCheckUpdate, internal.CheckUpdate)
router.POST(APIURLInternalDevicesIDSendInventory, internal.SendInventory)

Expand Down
10 changes: 10 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"

"github.com/mendersoftware/go-lib-micro/identity"

"github.com/mendersoftware/deviceconnect/client/inventory"
"github.com/mendersoftware/deviceconnect/client/workflows"
"github.com/mendersoftware/deviceconnect/model"
Expand Down Expand Up @@ -56,6 +58,7 @@ type App interface {
GetControlRecorder(ctx context.Context, sessionID string) io.Writer
DownloadFile(ctx context.Context, userID string, deviceID string, path string) error
UploadFile(ctx context.Context, userID string, deviceID string, path string) error
DeleteTenant(ctx context.Context, tenantID string) error
Shutdown(timeout time.Duration)
ShutdownDone()
RegisterShutdownCancel(context.CancelFunc) uint32
Expand Down Expand Up @@ -361,3 +364,10 @@ func (a *app) UnregisterShutdownCancel(id uint32) {
defer a.shutdownCancelsM.Unlock()
delete(a.shutdownCancels, id)
}

func (d *app) DeleteTenant(ctx context.Context, tenantID string) error {
tenantCtx := identity.WithContext(ctx, &identity.Identity{
Tenant: tenantID,
})
return d.store.DeleteTenant(tenantCtx, tenantID)
}
48 changes: 48 additions & 0 deletions app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package app
import (
"context"
"errors"
"fmt"
"io"
"testing"
"time"
Expand All @@ -30,6 +31,7 @@ import (
wf_mocks "github.com/mendersoftware/deviceconnect/client/workflows/mocks"
"github.com/mendersoftware/deviceconnect/model"
store_mocks "github.com/mendersoftware/deviceconnect/store/mocks"
"github.com/mendersoftware/go-lib-micro/identity"
)

func TestHealthCheck(t *testing.T) {
Expand Down Expand Up @@ -886,3 +888,49 @@ func TestShutdownCancels(t *testing.T) {

app.ShutdownDone()
}

func TestDeleteTenant(t *testing.T) {
t.Parallel()

testCases := []struct {
tenantId string

dbErr error
outErr string
}{
{
tenantId: "tenant1",
dbErr: errors.New("error"),
},
{
tenantId: "tenant2",
},
}

for i, tc := range testCases {
t.Run(fmt.Sprintf("tc %d", i), func(t *testing.T) {
t.Parallel()

ctx := context.Background()

ds := new(store_mocks.DataStore)
defer ds.AssertExpectations(t)
ds.On("DeleteTenant",
mock.MatchedBy(func(ctx context.Context) bool {
ident := identity.FromContext(ctx)
return assert.NotNil(t, ident) &&
assert.Equal(t, tc.tenantId, ident.Tenant)
}),
tc.tenantId,
).Return(tc.dbErr)
app := New(ds, nil, nil, Config{})
err := app.DeleteTenant(ctx, tc.tenantId)

if tc.dbErr != nil {
assert.EqualError(t, err, tc.dbErr.Error())
} else {
assert.NoError(t, err)
}
})
}
}
14 changes: 14 additions & 0 deletions app/mocks/App.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions docs/internal_api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ paths:
schema:
$ref: '#/components/schemas/Error'

/tenants/{tenantId}:
delete:
operationId: "Delete Tenant"
tags:
- Internal API
summary: Delete all the data for given tenant.
parameters:
- in: path
name: tenantId
schema:
type: string
required: true
description: ID of tenant.
responses:
204:
description: All the tenant data have been successfully deleted.
500:
$ref: '#/components/responses/InternalServerError'

/tenants/{tenantId}/devices:
post:
tags:
Expand Down
1 change: 1 addition & 0 deletions store/datastore.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type DataStore interface {
InsertSessionRecording(ctx context.Context, sessionID string, sessionBytes []byte) error
InsertControlRecording(ctx context.Context, sessionID string, sessionBytes []byte) error
DeleteSession(ctx context.Context, sessionID string) (*model.Session, error)
DeleteTenant(ctx context.Context, tenantID string) error
Close() error
}

Expand Down
14 changes: 14 additions & 0 deletions store/mocks/DataStore.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions store/mongo/datastore_mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,19 @@ func (db *DataStoreMongo) DropDatabase() error {
err := db.client.Database(DbName).Drop(ctx)
return err
}

func (db *DataStoreMongo) DeleteTenant(ctx context.Context, tenantID string) error {
database := db.client.Database(DbName)
collectionNames, err := database.ListCollectionNames(ctx, mopts.ListCollectionsOptions{})
if err != nil {
return err
}
for _, collName := range collectionNames {
collection := database.Collection(collName)
_, e := collection.DeleteMany(ctx, mstore.WithTenantID(ctx, bson.D{}))
if e != nil {
return e
}
}
return nil
}
29 changes: 29 additions & 0 deletions store/mongo/datastore_mongo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
mopts "go.mongodb.org/mongo-driver/mongo/options"
Expand Down Expand Up @@ -689,3 +690,31 @@ func TestSetSessionRecording(t *testing.T) {
})
}
}

func TestDeleteTenant(t *testing.T) {
if testing.Short() {
t.Skip("skipping TestDeleteTenant in short mode.")
}

const tenant = "foo"
deviceID := uuid.NewSHA1(uuid.NameSpaceDNS, []byte("mender.io")).String()

ctx := identity.WithContext(context.Background(),
&identity.Identity{
Tenant: tenant,
},
)

d := &DataStoreMongo{
client: db.Client(),
}
err := d.ProvisionDevice(ctx, tenant, deviceID)
require.NoError(t, err)

err = d.DeleteTenant(ctx, tenant)
assert.NoError(t, err)

dev, err := d.GetDevice(ctx, tenant, deviceID)
assert.Nil(t, dev)
assert.NoError(t, err)
}

0 comments on commit 8cfd4fe

Please sign in to comment.