github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/namespace/add.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 namespace
    22  
    23  import (
    24  	"bytes"
    25  	"fmt"
    26  	"net/http"
    27  	"path"
    28  
    29  	clusterclient "github.com/m3db/m3/src/cluster/client"
    30  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    31  	nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace"
    32  	"github.com/m3db/m3/src/dbnode/namespace"
    33  	"github.com/m3db/m3/src/query/api/v1/options"
    34  	"github.com/m3db/m3/src/query/api/v1/route"
    35  	"github.com/m3db/m3/src/query/api/v1/validators"
    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  	// M3DBAddURL is the url for the M3DB namespace add handler.
    48  	M3DBAddURL = path.Join(route.Prefix, M3DBServiceNamespacePathName)
    49  
    50  	// AddHTTPMethod is the HTTP method used with this resource.
    51  	AddHTTPMethod = http.MethodPost
    52  )
    53  
    54  // AddHandler is the handler for namespace adds.
    55  type AddHandler struct {
    56  	Handler
    57  
    58  	validator options.NamespaceValidator
    59  }
    60  
    61  // NewAddHandler returns a new instance of AddHandler.
    62  func NewAddHandler(
    63  	client clusterclient.Client,
    64  	instrumentOpts instrument.Options,
    65  	validator options.NamespaceValidator,
    66  ) *AddHandler {
    67  	return &AddHandler{
    68  		Handler: Handler{
    69  			client:         client,
    70  			instrumentOpts: instrumentOpts,
    71  		},
    72  		validator: validator,
    73  	}
    74  }
    75  
    76  func (h *AddHandler) ServeHTTP(
    77  	svc handleroptions.ServiceNameAndDefaults,
    78  	w http.ResponseWriter,
    79  	r *http.Request,
    80  ) {
    81  	ctx := r.Context()
    82  	logger := logging.WithContext(ctx, h.instrumentOpts)
    83  
    84  	md, rErr := h.parseRequest(r)
    85  	if rErr != nil {
    86  		logger.Error("unable to parse request", zap.Error(rErr))
    87  		xhttp.WriteError(w, rErr)
    88  		return
    89  	}
    90  
    91  	opts := handleroptions.NewServiceOptions(svc, r.Header, nil)
    92  	nsRegistry, err := h.Add(md, opts)
    93  	if err != nil {
    94  		if err == validators.ErrNamespaceExists {
    95  			logger.Error("namespace already exists", zap.Error(err))
    96  			xhttp.WriteError(w, xhttp.NewError(err, http.StatusConflict))
    97  			return
    98  		}
    99  
   100  		logger.Error("unable to add namespace", zap.Error(err))
   101  		xhttp.WriteError(w, err)
   102  		return
   103  	}
   104  
   105  	resp := &admin.NamespaceGetResponse{
   106  		Registry: &nsRegistry,
   107  	}
   108  
   109  	xhttp.WriteProtoMsgJSONResponse(w, resp, logger)
   110  }
   111  
   112  func (h *AddHandler) parseRequest(r *http.Request) (*admin.NamespaceAddRequest, error) {
   113  	defer r.Body.Close() // nolint:errcheck
   114  	rBody, err := xhttp.DurationToNanosBytes(r.Body)
   115  	if err != nil {
   116  		return nil, xerrors.NewInvalidParamsError(err)
   117  	}
   118  
   119  	addReq := new(admin.NamespaceAddRequest)
   120  	if err := jsonpb.Unmarshal(bytes.NewReader(rBody), addReq); err != nil {
   121  		return nil, xerrors.NewInvalidParamsError(err)
   122  	}
   123  
   124  	return addReq, nil
   125  }
   126  
   127  // Add adds a namespace.
   128  func (h *AddHandler) Add(
   129  	addReq *admin.NamespaceAddRequest,
   130  	opts handleroptions.ServiceOptions,
   131  ) (nsproto.Registry, error) {
   132  	var emptyReg nsproto.Registry
   133  
   134  	md, err := namespace.ToMetadata(addReq.Name, addReq.Options)
   135  	if err != nil {
   136  		return emptyReg, xerrors.NewInvalidParamsError(fmt.Errorf("bad namespace metadata: %v", err))
   137  	}
   138  
   139  	store, err := h.client.Store(opts.KVOverrideOptions())
   140  	if err != nil {
   141  		return emptyReg, err
   142  	}
   143  
   144  	currentMetadata, version, err := Metadata(store)
   145  	if err != nil {
   146  		return emptyReg, err
   147  	}
   148  
   149  	if err := h.validator.ValidateNewNamespace(md, currentMetadata); err != nil {
   150  		if err == validators.ErrNamespaceExists {
   151  			return emptyReg, err
   152  		}
   153  		return emptyReg, xerrors.NewInvalidParamsError(err)
   154  	}
   155  
   156  	newMDs := append(currentMetadata, md)
   157  	if err = validateNamespaceAggregationOptions(newMDs); err != nil {
   158  		return emptyReg, xerrors.NewInvalidParamsError(err)
   159  	}
   160  
   161  	nsMap, err := namespace.NewMap(newMDs)
   162  	if err != nil {
   163  		return emptyReg, xerrors.NewInvalidParamsError(err)
   164  	}
   165  
   166  	protoRegistry, err := namespace.ToProto(nsMap)
   167  	if err != nil {
   168  		return emptyReg, fmt.Errorf("error constructing namespace protobuf: %v", err)
   169  	}
   170  
   171  	_, err = store.CheckAndSet(M3DBNodeNamespacesKey, version, protoRegistry)
   172  	if err != nil {
   173  		return emptyReg, fmt.Errorf("failed to add namespace: %v", err)
   174  	}
   175  
   176  	return *protoRegistry, nil
   177  }