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  }