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 }