github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/namespace/schema.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  	"fmt"
    25  	"net/http"
    26  	"path"
    27  
    28  	clusterclient "github.com/m3db/m3/src/cluster/client"
    29  	"github.com/m3db/m3/src/cluster/kv"
    30  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    31  	"github.com/m3db/m3/src/dbnode/namespace/kvadmin"
    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  	"github.com/m3db/m3/src/x/instrument"
    37  	xhttp "github.com/m3db/m3/src/x/net/http"
    38  
    39  	"github.com/gogo/protobuf/jsonpb"
    40  	"go.uber.org/zap"
    41  )
    42  
    43  var (
    44  	// M3DBSchemaURL is the url for the M3DB schema handler.
    45  	M3DBSchemaURL = path.Join(route.Prefix, M3DBServiceSchemaPathName)
    46  
    47  	// SchemaDeployHTTPMethod is the HTTP method used to append to this resource.
    48  	SchemaDeployHTTPMethod = http.MethodPost
    49  )
    50  
    51  // SchemaHandler is the handler for namespace schema upserts.
    52  type SchemaHandler Handler
    53  
    54  // For unit test purpose.
    55  var newAdminService = kvadmin.NewAdminService
    56  
    57  // NewSchemaHandler returns a new instance of SchemaHandler.
    58  func NewSchemaHandler(
    59  	client clusterclient.Client,
    60  	instrumentOpts instrument.Options,
    61  ) *SchemaHandler {
    62  	return &SchemaHandler{
    63  		client:         client,
    64  		instrumentOpts: instrumentOpts,
    65  	}
    66  }
    67  
    68  func (h *SchemaHandler) ServeHTTP(
    69  	svc handleroptions.ServiceNameAndDefaults,
    70  	w http.ResponseWriter,
    71  	r *http.Request,
    72  ) {
    73  	ctx := r.Context()
    74  	logger := logging.WithContext(ctx, h.instrumentOpts)
    75  
    76  	md, rErr := h.parseRequest(r)
    77  	if rErr != nil {
    78  		logger.Error("unable to parse request", zap.Error(rErr))
    79  		xhttp.WriteError(w, rErr)
    80  		return
    81  	}
    82  
    83  	opts := handleroptions.NewServiceOptions(svc, r.Header, nil)
    84  	resp, err := h.Add(md, opts)
    85  	if err != nil {
    86  		if err == kv.ErrNotFound || xerrors.InnerError(err) == kv.ErrNotFound {
    87  			logger.Error("namespaces metadata key does not exist", zap.Error(err))
    88  			xhttp.WriteError(w, err)
    89  			return
    90  		}
    91  		if err == kvadmin.ErrNamespaceNotFound || xerrors.InnerError(err) == kvadmin.ErrNamespaceNotFound {
    92  			logger.Error("namespace does not exist", zap.Error(err))
    93  			xhttp.WriteError(w, xhttp.NewError(err, http.StatusNotFound))
    94  			return
    95  		}
    96  
    97  		logger.Error("unable to deploy schema to namespace", zap.Error(err))
    98  		xhttp.WriteError(w, err)
    99  		return
   100  	}
   101  
   102  	xhttp.WriteProtoMsgJSONResponse(w, &resp, logger)
   103  }
   104  
   105  func (h *SchemaHandler) parseRequest(r *http.Request) (*admin.NamespaceSchemaAddRequest, error) {
   106  	defer r.Body.Close()
   107  
   108  	var schemaAddReq admin.NamespaceSchemaAddRequest
   109  	if err := jsonpb.Unmarshal(r.Body, &schemaAddReq); err != nil {
   110  		return nil, xerrors.NewInvalidParamsError(err)
   111  	}
   112  	return &schemaAddReq, nil
   113  }
   114  
   115  // Add adds schema to an existing namespace.
   116  func (h *SchemaHandler) Add(
   117  	addReq *admin.NamespaceSchemaAddRequest,
   118  	opts handleroptions.ServiceOptions,
   119  ) (admin.NamespaceSchemaAddResponse, error) {
   120  	var emptyRep = admin.NamespaceSchemaAddResponse{}
   121  
   122  	store, err := h.client.Store(opts.KVOverrideOptions())
   123  	if err != nil {
   124  		return emptyRep, err
   125  	}
   126  
   127  	schemaAdmin := newAdminService(store, M3DBNodeNamespacesKey, nil)
   128  	deployID, err := schemaAdmin.DeploySchema(addReq.Name, addReq.ProtoName, addReq.MsgName, addReq.ProtoMap)
   129  	if err != nil {
   130  		return emptyRep, err
   131  	}
   132  	return admin.NamespaceSchemaAddResponse{DeployID: deployID}, nil
   133  }
   134  
   135  // SchemaResetHandler is the handler for namespace schema reset.
   136  type SchemaResetHandler Handler
   137  
   138  // NewSchemaResetHandler returns a new instance of SchemaHandler.
   139  func NewSchemaResetHandler(
   140  	client clusterclient.Client,
   141  	instrumentOpts instrument.Options,
   142  ) *SchemaResetHandler {
   143  	return &SchemaResetHandler{
   144  		client:         client,
   145  		instrumentOpts: instrumentOpts,
   146  	}
   147  }
   148  
   149  func (h *SchemaResetHandler) ServeHTTP(
   150  	svc handleroptions.ServiceNameAndDefaults,
   151  	w http.ResponseWriter,
   152  	r *http.Request,
   153  ) {
   154  	ctx := r.Context()
   155  	logger := logging.WithContext(ctx, h.instrumentOpts)
   156  
   157  	md, rErr := h.parseRequest(r)
   158  	if rErr != nil {
   159  		logger.Error("unable to parse request", zap.Error(rErr))
   160  		xhttp.WriteError(w, rErr)
   161  		return
   162  	}
   163  
   164  	opts := handleroptions.NewServiceOptions(svc, r.Header, nil)
   165  	resp, err := h.Reset(md, opts)
   166  	if err != nil {
   167  		if err == kv.ErrNotFound || xerrors.InnerError(err) == kv.ErrNotFound {
   168  			logger.Error("namespaces metadata key does not exist", zap.Error(err))
   169  			xhttp.WriteError(w, err)
   170  			return
   171  		}
   172  		if err == kvadmin.ErrNamespaceNotFound || xerrors.InnerError(err) == kvadmin.ErrNamespaceNotFound {
   173  			logger.Error("namespace does not exist", zap.Error(err))
   174  			xhttp.WriteError(w, xhttp.NewError(err, http.StatusNotFound))
   175  			return
   176  		}
   177  
   178  		logger.Error("unable to reset schema for namespace", zap.Error(err))
   179  		xhttp.WriteError(w, err)
   180  		return
   181  	}
   182  
   183  	xhttp.WriteProtoMsgJSONResponse(w, resp, logger)
   184  }
   185  
   186  func (h *SchemaResetHandler) parseRequest(r *http.Request) (*admin.NamespaceSchemaResetRequest, error) {
   187  	defer r.Body.Close()
   188  
   189  	var schemaResetReq admin.NamespaceSchemaResetRequest
   190  	if err := jsonpb.Unmarshal(r.Body, &schemaResetReq); err != nil {
   191  		return nil, xerrors.NewInvalidParamsError(err)
   192  	}
   193  	return &schemaResetReq, nil
   194  }
   195  
   196  // Reset resets schema for an existing namespace.
   197  func (h *SchemaResetHandler) Reset(
   198  	addReq *admin.NamespaceSchemaResetRequest,
   199  	opts handleroptions.ServiceOptions,
   200  ) (*admin.NamespaceSchemaResetResponse, error) {
   201  	var emptyRep = admin.NamespaceSchemaResetResponse{}
   202  	if !opts.Force {
   203  		err := fmt.Errorf("CAUTION! Reset schema will prevent proto-enabled namespace from loading, proceed if you know what you are doing, please retry with force set to true")
   204  		return &emptyRep, xerrors.NewInvalidParamsError(err)
   205  	}
   206  
   207  	store, err := h.client.Store(opts.KVOverrideOptions())
   208  	if err != nil {
   209  		return &emptyRep, err
   210  	}
   211  
   212  	schemaAdmin := newAdminService(store, M3DBNodeNamespacesKey, nil)
   213  	err = schemaAdmin.ResetSchema(addReq.Name)
   214  	if err != nil {
   215  		return &emptyRep, err
   216  	}
   217  	return &emptyRep, nil
   218  }