github.com/m3db/m3@v1.5.0/src/cluster/placementhandler/set.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  	"fmt"
    25  	"net/http"
    26  	"path"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/cluster/kv"
    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/gogo/protobuf/jsonpb"
    39  	"go.uber.org/zap"
    40  )
    41  
    42  const (
    43  	// SetHTTPMethod is the HTTP method for the the upsert endpoint.
    44  	SetHTTPMethod = http.MethodPost
    45  
    46  	setPathName = "set"
    47  )
    48  
    49  var (
    50  	// M3DBSetURL is the url for the m3db replace handler (method POST).
    51  	M3DBSetURL = path.Join(route.Prefix,
    52  		M3DBServicePlacementPathName, setPathName)
    53  
    54  	// M3AggSetURL is the url for the m3aggregator replace handler (method
    55  	// POST).
    56  	M3AggSetURL = path.Join(route.Prefix,
    57  		M3AggServicePlacementPathName, setPathName)
    58  
    59  	// M3CoordinatorSetURL is the url for the m3coordinator replace handler
    60  	// (method POST).
    61  	M3CoordinatorSetURL = path.Join(route.Prefix,
    62  		M3CoordinatorServicePlacementPathName, setPathName)
    63  )
    64  
    65  // SetHandler is the type for manually setting a placement value. If none
    66  // currently exists, this will set the initial placement. Otherwise it will
    67  // override the existing placement.
    68  type SetHandler Handler
    69  
    70  // NewSetHandler returns a new SetHandler.
    71  func NewSetHandler(opts HandlerOptions) *SetHandler {
    72  	return &SetHandler{HandlerOptions: opts, nowFn: time.Now}
    73  }
    74  
    75  func (h *SetHandler) ServeHTTP(
    76  	svc handleroptions.ServiceNameAndDefaults,
    77  	w http.ResponseWriter,
    78  	r *http.Request,
    79  ) {
    80  	ctx := r.Context()
    81  	logger := logging.WithContext(ctx, h.instrumentOptions)
    82  
    83  	req, err := h.parseRequest(r)
    84  	if err != nil {
    85  		logger.Error("unable to parse request", zap.Error(err))
    86  		xhttp.WriteError(w, err)
    87  		return
    88  	}
    89  
    90  	serviceOpts := handleroptions.NewServiceOptions(svc,
    91  		r.Header, h.m3AggServiceOptions)
    92  	service, _, err := ServiceWithAlgo(h.clusterClient,
    93  		serviceOpts, h.placement, h.nowFn(), nil)
    94  	if err != nil {
    95  		logger.Error("unable to create placement service", zap.Error(err))
    96  		xhttp.WriteError(w, err)
    97  		return
    98  	}
    99  
   100  	var isNewPlacement bool
   101  	curPlacement, err := service.Placement()
   102  	if err != nil {
   103  		if err != kv.ErrNotFound {
   104  			logger.Error("unable to get current placement", zap.Error(err))
   105  			xhttp.WriteError(w, err)
   106  			return
   107  		}
   108  
   109  		isNewPlacement = true
   110  		logger.Info("no placement found, creating new placement")
   111  	}
   112  
   113  	newPlacement, err := placement.NewPlacementFromProto(req.Placement)
   114  	if err != nil {
   115  		logger.Error("unable to create new placement from proto", zap.Error(err))
   116  		xhttp.WriteError(w, xhttp.NewError(err, http.StatusBadRequest))
   117  		return
   118  	}
   119  
   120  	if err := placement.Validate(newPlacement); err != nil {
   121  		if !req.Force {
   122  			logger.Error("unable to validate new placement", zap.Error(err))
   123  			xhttp.WriteError(w,
   124  				xerrors.NewRenamedError(err, fmt.Errorf("unable to validate new placement: %w", err)))
   125  			return
   126  		}
   127  		logger.Warn("unable to validate new placement, continuing with force", zap.Error(err))
   128  	}
   129  
   130  	var (
   131  		placementProto = req.Placement
   132  		dryRun         = !req.Confirm
   133  
   134  		updatedPlacement placement.Placement
   135  		placementVersion int
   136  	)
   137  
   138  	if dryRun {
   139  		logger.Info("performing dry run for set placement, not confirmed")
   140  		if isNewPlacement {
   141  			placementVersion = 0
   142  		} else {
   143  			placementVersion = curPlacement.Version() + 1
   144  		}
   145  	} else {
   146  		logger.Info("performing live run for set placement, confirmed")
   147  
   148  		if isNewPlacement {
   149  			updatedPlacement, err = service.SetIfNotExist(newPlacement)
   150  		} else {
   151  			// Ensure the placement we're updating is still the one on which we validated
   152  			// all shards are available.
   153  			updatedPlacement, err = service.CheckAndSet(newPlacement,
   154  				curPlacement.Version())
   155  		}
   156  
   157  		if err != nil {
   158  			logger.Error("unable to update placement", zap.Error(err), zap.Bool("isNewPlacement", isNewPlacement))
   159  			xhttp.WriteError(w, err)
   160  			return
   161  		}
   162  
   163  		placementVersion = updatedPlacement.Version()
   164  	}
   165  
   166  	resp := &admin.PlacementSetResponse{
   167  		Placement: placementProto,
   168  		Version:   int32(placementVersion),
   169  		DryRun:    dryRun,
   170  	}
   171  
   172  	xhttp.WriteProtoMsgJSONResponse(w, resp, logger)
   173  }
   174  
   175  func (h *SetHandler) parseRequest(r *http.Request) (*admin.PlacementSetRequest, error) {
   176  	defer r.Body.Close()
   177  
   178  	req := &admin.PlacementSetRequest{}
   179  	if err := jsonpb.Unmarshal(r.Body, req); err != nil {
   180  		return nil, xerrors.NewInvalidParamsError(err)
   181  	}
   182  
   183  	return req, nil
   184  }