github.com/m3db/m3@v1.5.0/src/cluster/placementhandler/delete.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package placementhandler
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"net/http"
    27  	"path"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/cluster/placement"
    31  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    32  	"github.com/m3db/m3/src/query/api/v1/route"
    33  	"github.com/m3db/m3/src/query/generated/proto/admin"
    34  	"github.com/m3db/m3/src/query/util/logging"
    35  	xerrors "github.com/m3db/m3/src/x/errors"
    36  	xhttp "github.com/m3db/m3/src/x/net/http"
    37  
    38  	"github.com/gorilla/mux"
    39  	"go.uber.org/zap"
    40  )
    41  
    42  const (
    43  	placementIDVar    = "id"
    44  	placementForceVar = "force"
    45  
    46  	// DeleteHTTPMethod is the HTTP method used with this resource.
    47  	DeleteHTTPMethod = http.MethodDelete
    48  )
    49  
    50  var (
    51  	placementIDPath = fmt.Sprintf("{%s}", placementIDVar)
    52  
    53  	// M3DBDeleteURL is the url for the placement delete handler for the M3DB service.
    54  	M3DBDeleteURL = path.Join(route.Prefix, M3DBServicePlacementPathName, placementIDPath)
    55  
    56  	// M3AggDeleteURL is the url for the placement delete handler for the M3Agg service.
    57  	M3AggDeleteURL = path.Join(route.Prefix, M3AggServicePlacementPathName, placementIDPath)
    58  
    59  	// M3CoordinatorDeleteURL is the url for the placement delete handler for the M3Coordinator service.
    60  	M3CoordinatorDeleteURL = path.Join(route.Prefix, M3CoordinatorServicePlacementPathName, placementIDPath)
    61  
    62  	errEmptyID = xerrors.NewInvalidParamsError(errors.New("must specify placement ID to delete"))
    63  )
    64  
    65  // DeleteHandler is the handler for placement deletes.
    66  type DeleteHandler Handler
    67  
    68  // NewDeleteHandler returns a new instance of DeleteHandler.
    69  func NewDeleteHandler(opts HandlerOptions) *DeleteHandler {
    70  	return &DeleteHandler{HandlerOptions: opts, nowFn: time.Now}
    71  }
    72  
    73  func (h *DeleteHandler) ServeHTTP(
    74  	svc handleroptions.ServiceNameAndDefaults,
    75  	w http.ResponseWriter,
    76  	r *http.Request,
    77  ) {
    78  	var (
    79  		ctx    = r.Context()
    80  		logger = logging.WithContext(ctx, h.instrumentOptions)
    81  		id     = mux.Vars(r)[placementIDVar]
    82  	)
    83  	if id == "" {
    84  		logger.Error("no placement ID provided to delete", zap.Error(errEmptyID))
    85  		xhttp.WriteError(w, errEmptyID)
    86  		return
    87  	}
    88  
    89  	var (
    90  		force = r.FormValue(placementForceVar) == "true"
    91  		opts  = handleroptions.NewServiceOptions(svc, r.Header, h.m3AggServiceOptions)
    92  	)
    93  
    94  	service, algo, err := ServiceWithAlgo(
    95  		h.clusterClient,
    96  		opts,
    97  		Handler(*h).PlacementConfig(),
    98  		h.nowFn(),
    99  		nil,
   100  	)
   101  	if err != nil {
   102  		xhttp.WriteError(w, err)
   103  		return
   104  	}
   105  
   106  	curPlacement, err := service.Placement()
   107  	if err != nil {
   108  		logger.Error("unable to fetch placement", zap.Error(err))
   109  		xhttp.WriteError(w, err)
   110  		return
   111  	}
   112  
   113  	instance, ok := curPlacement.Instance(id)
   114  	if !ok {
   115  		err = fmt.Errorf("instance not found: %s", id)
   116  		xhttp.WriteError(w, xhttp.NewError(err, http.StatusNotFound))
   117  		return
   118  	}
   119  
   120  	toRemove := []string{id}
   121  
   122  	// There are no unsafe placement changes because M3Coordinator is stateless
   123  	if isStateless(svc.ServiceName) {
   124  		force = true
   125  	}
   126  
   127  	var newPlacement placement.Placement
   128  	if force {
   129  		newPlacement, err = service.RemoveInstances(toRemove)
   130  		if err != nil {
   131  			logger.Error("unable to delete instances", zap.Error(err))
   132  			xhttp.WriteError(w, err)
   133  			return
   134  		}
   135  	} else {
   136  		if err := validateAllAvailable(curPlacement); err != nil {
   137  			logger.Warn("unable to remove instance, some shards not available", zap.Error(err), zap.String("instance", id))
   138  			xhttp.WriteError(w, xhttp.NewError(err, http.StatusBadRequest))
   139  			return
   140  		}
   141  
   142  		_, ok := curPlacement.Instance(id)
   143  		if !ok {
   144  			logger.Error("instance not found in placement", zap.String("instance", id))
   145  			err := fmt.Errorf("instance %s not found in placement", id)
   146  			xhttp.WriteError(w, xhttp.NewError(err, http.StatusNotFound))
   147  			return
   148  		}
   149  
   150  		newPlacement, err = algo.RemoveInstances(curPlacement, toRemove)
   151  		if err != nil {
   152  			logger.Error("unable to generate placement with instances removed", zap.String("instance", id), zap.Error(err))
   153  			xhttp.WriteError(w, err)
   154  			return
   155  		}
   156  
   157  		newPlacement, err = service.CheckAndSet(newPlacement, curPlacement.Version())
   158  		if err != nil {
   159  			logger.Error("unable to remove instance from placement", zap.String("instance", id), zap.Error(err))
   160  			xhttp.WriteError(w, err)
   161  			return
   162  		}
   163  	}
   164  
   165  	// Now need to delete aggregator related keys (e.g. for shardsets) if required.
   166  	if svc.ServiceName == handleroptions.M3AggregatorServiceName {
   167  		shardSetID := instance.ShardSetID()
   168  		anyExistingShardSetIDs := false
   169  		for _, elem := range curPlacement.Instances() {
   170  			if elem.ID() == instance.ID() {
   171  				continue
   172  			}
   173  			if elem.ShardSetID() == shardSetID {
   174  				anyExistingShardSetIDs = true
   175  				break
   176  			}
   177  		}
   178  
   179  		// Only delete the related shardset keys if no other shardsets left.
   180  		if !anyExistingShardSetIDs {
   181  			err := deleteAggregatorShardSetIDRelatedKeys(svc, opts,
   182  				h.clusterClient, []uint32{shardSetID})
   183  			if err != nil {
   184  				logger.Error("error removing aggregator keys for instances",
   185  					zap.Error(err))
   186  				xhttp.WriteError(w, err)
   187  				return
   188  			}
   189  		}
   190  	}
   191  
   192  	placementProto, err := newPlacement.Proto()
   193  	if err != nil {
   194  		logger.Error("unable to get placement protobuf", zap.Error(err))
   195  		xhttp.WriteError(w, err)
   196  		return
   197  	}
   198  
   199  	resp := &admin.PlacementGetResponse{
   200  		Placement: placementProto,
   201  		Version:   int32(newPlacement.Version()),
   202  	}
   203  
   204  	xhttp.WriteProtoMsgJSONResponse(w, resp, logger)
   205  }