github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/database/kvstore.go (about) 1 // Copyright (c) 2021 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 database 22 23 import ( 24 "bytes" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "io/ioutil" 29 "net/http" 30 31 "github.com/gogo/protobuf/jsonpb" 32 "go.uber.org/zap" 33 "google.golang.org/protobuf/runtime/protoiface" 34 35 clusterclient "github.com/m3db/m3/src/cluster/client" 36 "github.com/m3db/m3/src/cluster/generated/proto/commonpb" 37 "github.com/m3db/m3/src/cluster/generated/proto/kvpb" 38 "github.com/m3db/m3/src/cluster/kv" 39 nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace" 40 "github.com/m3db/m3/src/dbnode/kvconfig" 41 "github.com/m3db/m3/src/query/api/v1/options" 42 "github.com/m3db/m3/src/query/api/v1/route" 43 "github.com/m3db/m3/src/query/util/logging" 44 xerrors "github.com/m3db/m3/src/x/errors" 45 "github.com/m3db/m3/src/x/instrument" 46 xhttp "github.com/m3db/m3/src/x/net/http" 47 ) 48 49 const ( 50 // KeyValueStoreURL is the url to edit key/value configuration values. 51 KeyValueStoreURL = route.Prefix + "/kvstore" 52 // KeyValueStoreHTTPMethod is the HTTP method used with this resource. 53 KeyValueStoreHTTPMethod = http.MethodPost 54 ) 55 56 // KeyValueUpdate defines an update to a key's value. 57 type KeyValueUpdate struct { 58 // Key to update. 59 Key string `json:"key"` 60 // Value to update the key to. 61 Value json.RawMessage `json:"value"` 62 // Commit, if false, will not persist the update. If true, the 63 // update will be persisted. Used to test format of inputs. 64 Commit bool `json:"commit"` 65 } 66 67 // KeyValueUpdateResult defines the result of an update to a key's value. 68 type KeyValueUpdateResult struct { 69 // Key to update. 70 Key string `json:"key"` 71 // Old is the value before the update. 72 Old json.RawMessage `json:"old"` 73 // New is the value after the update. 74 New json.RawMessage `json:"new"` 75 // Version of the key. 76 Version int `json:"version"` 77 } 78 79 // KeyValueStoreHandler represents a handler for the key/value store endpoint 80 type KeyValueStoreHandler struct { 81 client clusterclient.Client 82 instrumentOpts instrument.Options 83 kvStoreProtoParser options.KVStoreProtoParser 84 } 85 86 // NewKeyValueStoreHandler returns a new instance of handler 87 func NewKeyValueStoreHandler( 88 client clusterclient.Client, 89 instrumentOpts instrument.Options, 90 kvStoreProtoParser options.KVStoreProtoParser, 91 ) http.Handler { 92 return &KeyValueStoreHandler{ 93 client: client, 94 instrumentOpts: instrumentOpts, 95 kvStoreProtoParser: kvStoreProtoParser, 96 } 97 } 98 99 func (h *KeyValueStoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 100 logger := logging.WithContext(r.Context(), h.instrumentOpts) 101 102 update, err := h.parseBody(r) 103 if err != nil { 104 logger.Error("unable to parse request", zap.Error(err)) 105 xhttp.WriteError(w, err) 106 return 107 } 108 109 kvStore, err := h.client.KV() 110 if err != nil { 111 logger.Error("unable to get kv store", zap.Error(err)) 112 xhttp.WriteError(w, err) 113 return 114 } 115 116 results, err := h.update(logger, kvStore, update) 117 if err != nil { 118 logger.Error("kv store error", 119 zap.Error(err), 120 zap.Any("update", update)) 121 xhttp.WriteError(w, err) 122 return 123 } 124 125 xhttp.WriteJSONResponse(w, results, logger) 126 } 127 128 func (h *KeyValueStoreHandler) parseBody(r *http.Request) (*KeyValueUpdate, error) { 129 body, err := ioutil.ReadAll(r.Body) 130 if err != nil { 131 return nil, xerrors.NewInvalidParamsError(err) 132 } 133 defer r.Body.Close() 134 135 var parsed KeyValueUpdate 136 if err := json.Unmarshal(body, &parsed); err != nil { 137 return nil, xerrors.NewInvalidParamsError(err) 138 } 139 140 return &parsed, nil 141 } 142 143 func (h *KeyValueStoreHandler) update( 144 logger *zap.Logger, 145 kvStore kv.Store, 146 update *KeyValueUpdate, 147 ) (*KeyValueUpdateResult, error) { 148 old, err := kvStore.Get(update.Key) 149 if err != nil && !errors.Is(err, kv.ErrNotFound) { 150 return nil, err 151 } 152 153 oldProto, err := h.newKVProtoMessage(update.Key) 154 if err != nil { 155 return nil, err 156 } 157 158 if old != nil { 159 if err := old.Unmarshal(oldProto); err != nil { 160 // Only log so we can overwrite corrupt existing entries. 161 logger.Error("cannot unmarshal old kv proto", zap.Error(err), zap.String("key", update.Key)) 162 } 163 } 164 165 newProto, err := h.newKVProtoMessage(update.Key) 166 if err != nil { 167 return nil, err 168 } 169 170 if err := jsonpb.UnmarshalString(string(update.Value), newProto); err != nil { 171 return nil, err 172 } 173 174 oldProtoMarshalled := bytes.NewBuffer(nil) 175 if err := (&jsonpb.Marshaler{}).Marshal(oldProtoMarshalled, oldProto); err != nil { 176 logger.Error("cannot unmarshal old kv proto", zap.Error(err), zap.String("key", update.Key)) 177 } 178 179 var version int 180 if update.Commit { 181 version, err = kvStore.Set(update.Key, newProto) 182 if err != nil { 183 return nil, err 184 } 185 } 186 187 result := KeyValueUpdateResult{ 188 Key: update.Key, 189 Old: oldProtoMarshalled.Bytes(), 190 New: update.Value, 191 Version: version, 192 } 193 194 logger.Info("kv store", zap.Any("update", *update), zap.Any("result", result)) 195 196 return &result, nil 197 } 198 199 func (h *KeyValueStoreHandler) newKVProtoMessage(key string) (protoiface.MessageV1, error) { 200 if h.kvStoreProtoParser != nil { 201 v, err := h.kvStoreProtoParser(key) 202 if err == nil { 203 return v, nil 204 } 205 } 206 207 switch key { 208 case kvconfig.NamespacesKey: 209 return &nsproto.Registry{}, nil 210 case kvconfig.ClusterNewSeriesInsertLimitKey: 211 case kvconfig.EncodersPerBlockLimitKey: 212 return &commonpb.Int64Proto{}, nil 213 case kvconfig.ClientBootstrapConsistencyLevel: 214 case kvconfig.ClientReadConsistencyLevel: 215 case kvconfig.ClientWriteConsistencyLevel: 216 return &commonpb.StringProto{}, nil 217 case kvconfig.QueryLimits: 218 return &kvpb.QueryLimits{}, nil 219 } 220 return nil, fmt.Errorf("unsupported kvstore key %s", key) 221 }