github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/namespace/ready.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  	"context"
    25  	"fmt"
    26  	"net/http"
    27  	"path"
    28  	"time"
    29  
    30  	"github.com/gogo/protobuf/jsonpb"
    31  	"go.uber.org/zap"
    32  
    33  	clusterclient "github.com/m3db/m3/src/cluster/client"
    34  	"github.com/m3db/m3/src/cluster/placementhandler/handleroptions"
    35  	"github.com/m3db/m3/src/dbnode/client"
    36  	nsproto "github.com/m3db/m3/src/dbnode/generated/proto/namespace"
    37  	"github.com/m3db/m3/src/dbnode/namespace"
    38  	"github.com/m3db/m3/src/dbnode/storage/index"
    39  	"github.com/m3db/m3/src/m3ninx/idx"
    40  	"github.com/m3db/m3/src/query/api/v1/route"
    41  	"github.com/m3db/m3/src/query/generated/proto/admin"
    42  	"github.com/m3db/m3/src/query/storage/m3"
    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/ident"
    46  	"github.com/m3db/m3/src/x/instrument"
    47  	xhttp "github.com/m3db/m3/src/x/net/http"
    48  )
    49  
    50  const (
    51  	defaultReadyContextTimeout = 10 * time.Second
    52  )
    53  
    54  var (
    55  	// M3DBReadyURL is the url for the M3DB namespace mark_ready handler.
    56  	M3DBReadyURL = path.Join(route.Prefix, M3DBServiceNamespacePathName, "ready")
    57  
    58  	// ReadyHTTPMethod is the HTTP method used with this resource.
    59  	ReadyHTTPMethod = http.MethodPost
    60  )
    61  
    62  // ReadyHandler is the handler for marking namespaces ready.
    63  type ReadyHandler Handler
    64  
    65  // NewReadyHandler returns a new instance of ReadyHandler.
    66  func NewReadyHandler(
    67  	client clusterclient.Client,
    68  	clusters m3.Clusters,
    69  	instrumentOpts instrument.Options,
    70  ) *ReadyHandler {
    71  	return &ReadyHandler{
    72  		client:         client,
    73  		clusters:       clusters,
    74  		instrumentOpts: instrumentOpts,
    75  	}
    76  }
    77  
    78  func (h *ReadyHandler) ServeHTTP(
    79  	svc handleroptions.ServiceNameAndDefaults,
    80  	w http.ResponseWriter,
    81  	r *http.Request,
    82  ) {
    83  	ctx := r.Context()
    84  	if _, ok := ctx.Deadline(); !ok {
    85  		var cancel context.CancelFunc
    86  		ctx, cancel = context.WithTimeout(ctx, defaultReadyContextTimeout)
    87  		defer cancel()
    88  	}
    89  
    90  	logger := logging.WithContext(ctx, h.instrumentOpts)
    91  
    92  	req, rErr := h.parseRequest(r)
    93  	if rErr != nil {
    94  		logger.Error("unable to parse request", zap.Error(rErr))
    95  		xhttp.WriteError(w, rErr)
    96  		return
    97  	}
    98  
    99  	opts := handleroptions.NewServiceOptions(svc, r.Header, nil)
   100  	ready, err := h.ready(ctx, req, opts)
   101  	if err != nil {
   102  		logger.Error("unable to mark namespace as ready", zap.Error(err))
   103  		xhttp.WriteError(w, err)
   104  		return
   105  	}
   106  
   107  	resp := &admin.NamespaceReadyResponse{
   108  		Ready: ready,
   109  	}
   110  
   111  	xhttp.WriteProtoMsgJSONResponse(w, resp, logger)
   112  }
   113  
   114  func (h *ReadyHandler) parseRequest(r *http.Request) (*admin.NamespaceReadyRequest, error) {
   115  	defer r.Body.Close()
   116  
   117  	req := new(admin.NamespaceReadyRequest)
   118  	if err := jsonpb.Unmarshal(r.Body, req); err != nil {
   119  		return nil, xerrors.NewInvalidParamsError(err)
   120  	}
   121  
   122  	return req, nil
   123  }
   124  
   125  func (h *ReadyHandler) ready(
   126  	ctx context.Context,
   127  	req *admin.NamespaceReadyRequest,
   128  	opts handleroptions.ServiceOptions,
   129  ) (bool, error) {
   130  	// NB(nate): Readying a namespace only applies to namespaces created dynamically. As such,
   131  	// ensure that any calls to the ready endpoint simply return true when using static configuration
   132  	// as namespaces are ready by default in this case.
   133  	if h.clusters != nil && h.clusters.ConfigType() == m3.ClusterConfigTypeStatic {
   134  		h.instrumentOpts.Logger().Debug(
   135  			"/namespace/ready endpoint not supported for statically configured namespaces.",
   136  		)
   137  		return true, nil
   138  	}
   139  
   140  	// Fetch existing namespace metadata.
   141  	store, err := h.client.Store(opts.KVOverrideOptions())
   142  	if err != nil {
   143  		return false, err
   144  	}
   145  
   146  	metadata, version, err := Metadata(store)
   147  	if err != nil {
   148  		return false, err
   149  	}
   150  
   151  	// Find the desired namespace.
   152  	newMetadata := make(map[string]namespace.Metadata)
   153  	for _, ns := range metadata {
   154  		newMetadata[ns.ID().String()] = ns
   155  	}
   156  
   157  	ns, ok := newMetadata[req.Name]
   158  	if !ok {
   159  		return false, xerrors.NewInvalidParamsError(fmt.Errorf("namespace %v not found", req.Name))
   160  	}
   161  
   162  	// Just return if namespace is already ready.
   163  	currentState := ns.Options().StagingState()
   164  	if currentState.Status() == namespace.ReadyStagingStatus {
   165  		return true, nil
   166  	}
   167  
   168  	// If we're not forcing the staging state, check db nodes to see if namespace is ready.
   169  	if !req.Force {
   170  		if err := h.checkDBNodes(ctx, req.Name); err != nil {
   171  			return false, err
   172  		}
   173  	}
   174  
   175  	// Update staging state status to ready.
   176  	state, err := namespace.NewStagingState(nsproto.StagingStatus_READY)
   177  	if err != nil {
   178  		return false, err
   179  	}
   180  	newOpts := ns.Options().SetStagingState(state)
   181  	newNs, err := namespace.NewMetadata(ns.ID(), newOpts)
   182  	if err != nil {
   183  		return false, err
   184  	}
   185  
   186  	newMetadata[req.Name] = newNs
   187  
   188  	newMds := make([]namespace.Metadata, 0, len(newMetadata))
   189  	for _, elem := range newMetadata {
   190  		newMds = append(newMds, elem)
   191  	}
   192  
   193  	nsMap, err := namespace.NewMap(newMds)
   194  	if err != nil {
   195  		return false, err
   196  	}
   197  
   198  	protoRegistry, err := namespace.ToProto(nsMap)
   199  	if err != nil {
   200  		return false, err
   201  	}
   202  
   203  	if _, err = store.CheckAndSet(M3DBNodeNamespacesKey, version, protoRegistry); err != nil {
   204  		return false, err
   205  	}
   206  
   207  	return true, nil
   208  }
   209  
   210  func (h *ReadyHandler) checkDBNodes(ctx context.Context, namespace string) error {
   211  	if h.clusters == nil {
   212  		err := fmt.Errorf("coordinator is not connected to dbnodes. cannot check namespace %v"+
   213  			" for readiness. set force = true to make namespaces ready without checking dbnodes", namespace)
   214  		return xerrors.NewInvalidParamsError(err)
   215  	}
   216  
   217  	var (
   218  		session client.Session
   219  		id      ident.ID
   220  	)
   221  	for _, clusterNamespace := range h.clusters.NonReadyClusterNamespaces() {
   222  		if clusterNamespace.NamespaceID().String() == namespace {
   223  			session = clusterNamespace.Session()
   224  			id = clusterNamespace.NamespaceID()
   225  			break
   226  		}
   227  	}
   228  	if session == nil {
   229  		err := fmt.Errorf("could not find db session for namespace: %v", namespace)
   230  		return xerrors.NewInvalidParamsError(err)
   231  	}
   232  
   233  	// Do a simple quorum read. A non-error indicates most dbnodes have the namespace.
   234  	_, _, err := session.FetchTaggedIDs(ctx, id,
   235  		index.Query{Query: idx.NewAllQuery()},
   236  		index.QueryOptions{SeriesLimit: 1, DocsLimit: 1})
   237  	// We treat any error here as a proxy for namespace readiness.
   238  	if err != nil {
   239  		return fmt.Errorf("namepace %v not yet ready, err: %w", namespace, err)
   240  	}
   241  
   242  	return nil
   243  }