github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/namespace/update.go (about) 1 // Copyright (c) 2020 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 namespace 22 23 import ( 24 "bytes" 25 "errors" 26 "fmt" 27 "net/http" 28 "path" 29 "reflect" 30 31 clusterclient "github.com/m3db/m3/src/cluster/client" 32 "github.com/m3db/m3/src/cluster/placementhandler/handleroptions" 33 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 34 "github.com/m3db/m3/src/dbnode/namespace" 35 "github.com/m3db/m3/src/query/api/v1/route" 36 "github.com/m3db/m3/src/query/generated/proto/admin" 37 "github.com/m3db/m3/src/query/util/logging" 38 xerrors "github.com/m3db/m3/src/x/errors" 39 "github.com/m3db/m3/src/x/instrument" 40 xhttp "github.com/m3db/m3/src/x/net/http" 41 42 "github.com/gogo/protobuf/jsonpb" 43 "go.uber.org/zap" 44 ) 45 46 var ( 47 // M3DBUpdateURL is the url for the M3DB namespace update handler. 48 M3DBUpdateURL = path.Join(route.Prefix, M3DBServiceNamespacePathName) 49 50 // UpdateHTTPMethod is the HTTP method used with this resource. 51 UpdateHTTPMethod = http.MethodPut 52 53 fieldNameRetentionOptions = "RetentionOptions" 54 fieldNameRetentionPeriod = "RetentionPeriodNanos" 55 fieldNameRuntimeOptions = "RuntimeOptions" 56 fieldNameAggregationOptions = "AggregationOptions" 57 fieldNameExtendedOptions = "ExtendedOptions" 58 59 errEmptyNamespaceName = errors.New("must specify namespace name") 60 errEmptyNamespaceOptions = errors.New("update options cannot be empty") 61 errNamespaceFieldImmutable = errors.New("namespace option field is immutable") 62 63 allowedUpdateOptionsFields = map[string]struct{}{ 64 fieldNameRetentionOptions: {}, 65 fieldNameRuntimeOptions: {}, 66 fieldNameAggregationOptions: {}, 67 fieldNameExtendedOptions: {}, 68 } 69 ) 70 71 // UpdateHandler is the handler for namespace updates. 72 type UpdateHandler Handler 73 74 // NewUpdateHandler returns a new instance of UpdateHandler. 75 func NewUpdateHandler( 76 client clusterclient.Client, 77 instrumentOpts instrument.Options, 78 ) *UpdateHandler { 79 return &UpdateHandler{ 80 client: client, 81 instrumentOpts: instrumentOpts, 82 } 83 } 84 85 func (h *UpdateHandler) ServeHTTP( 86 svc handleroptions.ServiceNameAndDefaults, 87 w http.ResponseWriter, 88 r *http.Request, 89 ) { 90 ctx := r.Context() 91 logger := logging.WithContext(ctx, h.instrumentOpts) 92 93 md, rErr := h.parseRequest(r) 94 if rErr != nil { 95 logger.Warn("unable to parse request", zap.Error(rErr)) 96 xhttp.WriteError(w, rErr) 97 return 98 } 99 100 opts := handleroptions.NewServiceOptions(svc, r.Header, nil) 101 nsRegistry, err := h.Update(md, opts) 102 if err != nil { 103 logger.Error("unable to update namespace", zap.Error(err)) 104 xhttp.WriteError(w, err) 105 return 106 } 107 108 resp := &admin.NamespaceGetResponse{ 109 Registry: &nsRegistry, 110 } 111 112 xhttp.WriteProtoMsgJSONResponse(w, resp, logger) 113 } 114 115 func (h *UpdateHandler) parseRequest(r *http.Request) (*admin.NamespaceUpdateRequest, error) { 116 defer r.Body.Close() 117 rBody, err := xhttp.DurationToNanosBytes(r.Body) 118 if err != nil { 119 return nil, xerrors.NewInvalidParamsError(err) 120 } 121 122 updateReq := new(admin.NamespaceUpdateRequest) 123 if err := jsonpb.Unmarshal(bytes.NewReader(rBody), updateReq); err != nil { 124 return nil, xerrors.NewInvalidParamsError(err) 125 } 126 127 if err := validateUpdateRequest(updateReq); err != nil { 128 err := fmt.Errorf("unable to validate update request: %w", err) 129 return nil, xerrors.NewInvalidParamsError(err) 130 } 131 132 return updateReq, nil 133 } 134 135 // Ensure that only fields we allow to be updated (e.g. retention period) are 136 // non-zero. Uses reflection to be resilient against adding more immutable 137 // fields to namespaceOptions but forgetting to validate them here. 138 func validateUpdateRequest(req *admin.NamespaceUpdateRequest) error { 139 if req.Name == "" { 140 return errEmptyNamespaceName 141 } 142 143 if req.Options == nil { 144 return errEmptyNamespaceOptions 145 } 146 147 optsVal := reflect.ValueOf(*req.Options) 148 allNonZeroFields := true 149 for i := 0; i < optsVal.NumField(); i++ { 150 field := optsVal.Field(i) 151 fieldName := optsVal.Type().Field(i).Name 152 if field.IsZero() { 153 continue 154 } 155 156 allNonZeroFields = false 157 158 _, ok := allowedUpdateOptionsFields[fieldName] 159 if !ok { 160 return fmt.Errorf("%s: %w", fieldName, errNamespaceFieldImmutable) 161 } 162 } 163 164 if allNonZeroFields { 165 return errEmptyNamespaceOptions 166 } 167 168 if opts := req.Options.RetentionOptions; opts != nil { 169 optsVal := reflect.ValueOf(*opts) 170 for i := 0; i < optsVal.NumField(); i++ { 171 field := optsVal.Field(i) 172 fieldName := optsVal.Type().Field(i).Name 173 if !field.IsZero() && fieldName != fieldNameRetentionPeriod { 174 return fmt.Errorf("%s.%s: %w", fieldNameRetentionOptions, fieldName, errNamespaceFieldImmutable) 175 } 176 } 177 } 178 179 return nil 180 } 181 182 // Update updates a namespace. 183 func (h *UpdateHandler) Update( 184 updateReq *admin.NamespaceUpdateRequest, 185 opts handleroptions.ServiceOptions, 186 ) (nsproto.Registry, error) { 187 var emptyReg nsproto.Registry 188 189 store, err := h.client.Store(opts.KVOverrideOptions()) 190 if err != nil { 191 return emptyReg, err 192 } 193 194 currentMetadata, version, err := Metadata(store) 195 if err != nil { 196 return emptyReg, err 197 } 198 199 newMetadata := make(map[string]namespace.Metadata) 200 for _, ns := range currentMetadata { 201 newMetadata[ns.ID().String()] = ns 202 } 203 204 ns, ok := newMetadata[updateReq.Name] 205 if !ok { 206 return emptyReg, xhttp.NewError( 207 fmt.Errorf("namespace not found: err=%s", updateReq.Name), 208 http.StatusNotFound) 209 } 210 211 // Replace targeted namespace with modified retention. 212 if newRetentionOpts := updateReq.Options.RetentionOptions; newRetentionOpts != nil { 213 if newNanos := newRetentionOpts.RetentionPeriodNanos; newNanos != 0 { 214 dur := namespace.FromNanos(newNanos) 215 retentionOpts := ns.Options().RetentionOptions(). 216 SetRetentionPeriod(dur) 217 opts := ns.Options(). 218 SetRetentionOptions(retentionOpts) 219 ns, err = namespace.NewMetadata(ns.ID(), opts) 220 if err != nil { 221 return emptyReg, xerrors.NewInvalidParamsError(fmt.Errorf( 222 "error constructing new metadata: %w", err)) 223 } 224 } 225 } 226 227 // Update runtime options. 228 if newRuntimeOpts := updateReq.Options.RuntimeOptions; newRuntimeOpts != nil { 229 runtimeOpts := ns.Options().RuntimeOptions() 230 if v := newRuntimeOpts.WriteIndexingPerCPUConcurrency; v != nil { 231 runtimeOpts = runtimeOpts.SetWriteIndexingPerCPUConcurrency(&v.Value) 232 } 233 if v := newRuntimeOpts.FlushIndexingPerCPUConcurrency; v != nil { 234 runtimeOpts = runtimeOpts.SetFlushIndexingPerCPUConcurrency(&v.Value) 235 } 236 opts := ns.Options(). 237 SetRuntimeOptions(runtimeOpts) 238 ns, err = namespace.NewMetadata(ns.ID(), opts) 239 if err != nil { 240 return emptyReg, xerrors.NewInvalidParamsError(fmt.Errorf( 241 "error constructing new metadata: %w", err)) 242 } 243 } 244 245 // Update extended options. 246 if newExtendedOptions := updateReq.Options.ExtendedOptions; newExtendedOptions != nil { 247 newExtOpts, err := namespace.ToExtendedOptions(newExtendedOptions) 248 if err != nil { 249 return emptyReg, xerrors.NewInvalidParamsError(err) 250 } 251 opts := ns.Options().SetExtendedOptions(newExtOpts) 252 ns, err = namespace.NewMetadata(ns.ID(), opts) 253 if err != nil { 254 return emptyReg, xerrors.NewInvalidParamsError(fmt.Errorf( 255 "error constructing new metadata: %w", err)) 256 } 257 } 258 if protoAggOpts := updateReq.Options.AggregationOptions; protoAggOpts != nil { 259 newAggOpts, err := namespace.ToAggregationOptions(protoAggOpts) 260 if err != nil { 261 return emptyReg, xerrors.NewInvalidParamsError(fmt.Errorf( 262 "error constructing construction aggregationOptions: %w", err)) 263 } 264 if !ns.Options().AggregationOptions().Equal(newAggOpts) { 265 opts := ns.Options().SetAggregationOptions(newAggOpts) 266 ns, err = namespace.NewMetadata(ns.ID(), opts) 267 if err != nil { 268 return emptyReg, xerrors.NewInvalidParamsError(fmt.Errorf( 269 "error constructing new metadata: %w", err)) 270 } 271 } 272 } 273 274 // Update the namespace in case an update occurred. 275 newMetadata[updateReq.Name] = ns 276 277 // Set the new slice and update. 278 newMDs := make([]namespace.Metadata, 0, len(newMetadata)) 279 for _, elem := range newMetadata { 280 newMDs = append(newMDs, elem) 281 } 282 283 if err = validateNamespaceAggregationOptions(newMDs); err != nil { 284 return emptyReg, xerrors.NewInvalidParamsError(err) 285 } 286 287 nsMap, err := namespace.NewMap(newMDs) 288 if err != nil { 289 return emptyReg, xerrors.NewInvalidParamsError(err) 290 } 291 292 protoRegistry, err := namespace.ToProto(nsMap) 293 if err != nil { 294 return emptyReg, fmt.Errorf("error constructing namespace protobuf: %w", err) 295 } 296 297 _, err = store.CheckAndSet(M3DBNodeNamespacesKey, version, protoRegistry) 298 if err != nil { 299 return emptyReg, fmt.Errorf("failed to update namespace: %w", err) 300 } 301 302 return *protoRegistry, nil 303 }