Skip to content

Commit

Permalink
[query] topic api: support DELETE on /topic (#1926)
Browse files Browse the repository at this point in the history
Users may need to delete m3msg topics.
  • Loading branch information
schallert authored Sep 5, 2019
1 parent b5db601 commit ef06251
Show file tree
Hide file tree
Showing 5 changed files with 234 additions and 27 deletions.
5 changes: 4 additions & 1 deletion src/query/api/v1/handler/topic/common.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018 Uber Technologies, Inc.
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -84,6 +84,9 @@ func RegisterRoutes(
r.HandleFunc(AddURL,
wrapped(NewAddHandler(client, cfg, instrumentOpts)).ServeHTTP).
Methods(AddHTTPMethod)
r.HandleFunc(DeleteURL,
wrapped(NewDeleteHandler(client, cfg, instrumentOpts)).ServeHTTP).
Methods(DeleteHTTPMethod)
}

func topicName(headers http.Header) string {
Expand Down
56 changes: 56 additions & 0 deletions src/query/api/v1/handler/topic/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package topic

import (
"testing"

clusterclient "github.com/m3db/m3/src/cluster/client"
"github.com/m3db/m3/src/msg/generated/proto/topicpb"
"github.com/m3db/m3/src/msg/topic"

"github.com/gogo/protobuf/jsonpb"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
)

var (
jsonMarshaler = jsonpb.Marshaler{EmitDefaults: true, Indent: " "}
jsonUnmarshaler = jsonpb.Unmarshaler{AllowUnknownFields: false}
)

func validateEqualTopicProto(t *testing.T, this, other topicpb.Topic) {
t1, err := topic.NewTopicFromProto(&this)
require.NoError(t, err)
t2, err := topic.NewTopicFromProto(&other)
require.NoError(t, err)
require.Equal(t, t1, t2)
}

func testServiceFn(s topic.Service) serviceFn {
return func(clusterClient clusterclient.Client) (topic.Service, error) {
return s, nil
}
}

func setupTest(t *testing.T, ctrl *gomock.Controller) *topic.MockService {
return topic.NewMockService(ctrl)
}
88 changes: 88 additions & 0 deletions src/query/api/v1/handler/topic/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package topic

import (
"net/http"

clusterclient "github.com/m3db/m3/src/cluster/client"
"github.com/m3db/m3/src/cmd/services/m3query/config"
"github.com/m3db/m3/src/query/api/v1/handler"
"github.com/m3db/m3/src/query/util/logging"
"github.com/m3db/m3/src/x/instrument"
xhttp "github.com/m3db/m3/src/x/net/http"

pkgerrors "github.com/pkg/errors"
"go.uber.org/zap"
)

const (
// DeleteURL is the url for the topic delete handler (with the DELETE method).
DeleteURL = handler.RoutePrefixV1 + "/topic"

// DeleteHTTPMethod is the HTTP method used with this resource.
DeleteHTTPMethod = http.MethodDelete
)

// DeleteHandler is the handler for topic adds.
type DeleteHandler Handler

// NewDeleteHandler returns a new instance of DeleteHandler.
func NewDeleteHandler(
client clusterclient.Client,
cfg config.Configuration,
instrumentOpts instrument.Options,
) *DeleteHandler {
return &DeleteHandler{
client: client,
cfg: cfg,
serviceFn: Service,
instrumentOpts: instrumentOpts,
}
}

func (h *DeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
logger = logging.WithContext(ctx, h.instrumentOpts)
)

service, err := h.serviceFn(h.client)
if err != nil {
logger.Error("unable to get service", zap.Error(err))
xhttp.Error(w, err, http.StatusInternalServerError)
return
}

name := topicName(r.Header)
svcLogger := logger.With(zap.String("service", name))
if err := service.Delete(name); err != nil {
svcLogger.Error("unable to delete service", zap.Error(err))
err := pkgerrors.WithMessagef(err, "error deleting service '%s'", name)
xhttp.Error(w, err, http.StatusBadRequest)
return
}

svcLogger.Info("deleted service")
// This is technically not necessary but we prefer to be verbose in handler
// logic.
w.WriteHeader(http.StatusOK)
}
85 changes: 85 additions & 0 deletions src/query/api/v1/handler/topic/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package topic

import (
"errors"
"net/http"
"net/http/httptest"
"testing"

"github.com/m3db/m3/src/cmd/services/m3query/config"
"github.com/m3db/m3/src/x/instrument"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestTopicDeleteHandler(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockService := setupTest(t, ctrl)
handler := NewDeleteHandler(nil, config.Configuration{}, instrument.NewOptions())
handler.serviceFn = testServiceFn(mockService)

// Test successful get
w := httptest.NewRecorder()
req := httptest.NewRequest("DELETE", "/topic", nil)
require.NotNil(t, req)

mockService.
EXPECT().
Delete(DefaultTopicName).
Return(nil)
handler.ServeHTTP(w, req)

resp := w.Result()
assert.Equal(t, http.StatusOK, resp.StatusCode)

w = httptest.NewRecorder()
req = httptest.NewRequest("DELETE", "/topic", nil)
req.Header.Add(HeaderTopicName, "foobar")
require.NotNil(t, req)

mockService.
EXPECT().
Delete("foobar").
Return(nil)
handler.ServeHTTP(w, req)

resp = w.Result()
assert.Equal(t, http.StatusOK, resp.StatusCode)

w = httptest.NewRecorder()
req = httptest.NewRequest("DELETE", "/topic", nil)
require.NotNil(t, req)

mockService.
EXPECT().
Delete(DefaultTopicName).
Return(errors.New("not found"))
handler.ServeHTTP(w, req)

resp = w.Result()
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
}
27 changes: 1 addition & 26 deletions src/query/api/v1/handler/topic/get_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) 2018 Uber Technologies, Inc.
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
Expand Down Expand Up @@ -28,23 +28,16 @@ import (
"net/http/httptest"
"testing"

clusterclient "github.com/m3db/m3/src/cluster/client"
"github.com/m3db/m3/src/cmd/services/m3query/config"
"github.com/m3db/m3/src/msg/generated/proto/topicpb"
"github.com/m3db/m3/src/msg/topic"
"github.com/m3db/m3/src/query/generated/proto/admin"
"github.com/m3db/m3/src/x/instrument"

"github.com/gogo/protobuf/jsonpb"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
)

var (
jsonMarshaler = jsonpb.Marshaler{EmitDefaults: true, Indent: " "}
jsonUnmarshaler = jsonpb.Unmarshaler{AllowUnknownFields: false}
)

func TestTopicGetHandler(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Expand Down Expand Up @@ -94,21 +87,3 @@ func TestTopicGetHandler(t *testing.T) {
resp = w.Result()
require.Equal(t, http.StatusNotFound, resp.StatusCode)
}

func validateEqualTopicProto(t *testing.T, this, other topicpb.Topic) {
t1, err := topic.NewTopicFromProto(&this)
require.NoError(t, err)
t2, err := topic.NewTopicFromProto(&other)
require.NoError(t, err)
require.Equal(t, t1, t2)
}

func testServiceFn(s topic.Service) serviceFn {
return func(clusterClient clusterclient.Client) (topic.Service, error) {
return s, nil
}
}

func setupTest(t *testing.T, ctrl *gomock.Controller) *topic.MockService {
return topic.NewMockService(ctrl)
}

0 comments on commit ef06251

Please sign in to comment.