github.com/m3db/m3@v1.5.0/src/query/api/v1/handler/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 handler
    22  
    23  import (
    24  	"encoding/json"
    25  	"fmt"
    26  	"net/http"
    27  	"strconv"
    28  
    29  	"go.uber.org/zap"
    30  
    31  	"github.com/m3db/m3/src/query/api/v1/options"
    32  	"github.com/m3db/m3/src/query/storage/m3"
    33  	"github.com/m3db/m3/src/query/util/logging"
    34  	xerrors "github.com/m3db/m3/src/x/errors"
    35  	"github.com/m3db/m3/src/x/instrument"
    36  	xhttp "github.com/m3db/m3/src/x/net/http"
    37  )
    38  
    39  const (
    40  	// ReadyURL is the url to check for readiness.
    41  	ReadyURL = "/ready"
    42  
    43  	// ReadyHTTPMethod is the HTTP method used with this resource.
    44  	ReadyHTTPMethod = http.MethodGet
    45  )
    46  
    47  // ReadyHandler tests whether the service is connected to underlying storage.
    48  type ReadyHandler struct {
    49  	clusters       m3.Clusters
    50  	instrumentOpts instrument.Options
    51  }
    52  
    53  // NewReadyHandler returns a new instance of handler.
    54  func NewReadyHandler(opts options.HandlerOptions) http.Handler {
    55  	return &ReadyHandler{
    56  		clusters:       opts.Clusters(),
    57  		instrumentOpts: opts.InstrumentOpts(),
    58  	}
    59  }
    60  
    61  type readyResultNamespace struct {
    62  	ID         string                         `json:"id"`
    63  	Attributes readyResultNamespaceAttributes `json:"attributes"`
    64  }
    65  
    66  type readyResultNamespaceAttributes struct {
    67  	MetricsType string `json:"metricsType"`
    68  	Retention   string `json:"retention"`
    69  	Resolution  string `json:"resolution"`
    70  }
    71  
    72  type readyResult struct {
    73  	ReadyReads     []readyResultNamespace `json:"readyReads,omitempty"`
    74  	NotReadyReads  []readyResultNamespace `json:"notReadyReads,omitempty"`
    75  	ReadyWrites    []readyResultNamespace `json:"readyWrites,omitempty"`
    76  	NotReadyWrites []readyResultNamespace `json:"notReadyWrites,omitempty"`
    77  }
    78  
    79  // ServeHTTP serves HTTP handler. This comment only here so doesn't break
    80  // lint by not being "ServeHTTP" as the comment above this function
    81  // which needs // nolint:gocyclo.
    82  // nolint:gocyclo
    83  func (h *ReadyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    84  	logger := logging.WithContext(r.Context(), h.instrumentOpts)
    85  
    86  	req, err := h.parseReadyRequestChecked(r)
    87  	if err != nil {
    88  		logger.Error("unable to parse ready request", zap.Error(err))
    89  		xhttp.WriteError(w, err)
    90  		return
    91  	}
    92  
    93  	var namespaces m3.ClusterNamespaces
    94  	if h.clusters != nil {
    95  		namespaces = h.clusters.ClusterNamespaces()
    96  	}
    97  
    98  	result := &readyResult{}
    99  	for _, ns := range namespaces {
   100  		attrs := ns.Options().Attributes()
   101  		nsResult := readyResultNamespace{
   102  			ID: ns.NamespaceID().String(),
   103  			Attributes: readyResultNamespaceAttributes{
   104  				MetricsType: attrs.MetricsType.String(),
   105  				Retention:   attrs.Retention.String(),
   106  				Resolution:  attrs.Resolution.String(),
   107  			},
   108  		}
   109  
   110  		ready, err := ns.Session().ReadClusterAvailability()
   111  		if err != nil {
   112  			logger.Error("check read availability error", zap.Error(err))
   113  			xhttp.WriteError(w, err)
   114  			return
   115  		}
   116  		if !ready {
   117  			result.NotReadyReads = append(result.NotReadyReads, nsResult)
   118  		} else {
   119  			result.ReadyReads = append(result.ReadyReads, nsResult)
   120  		}
   121  
   122  		ready, err = ns.Session().WriteClusterAvailability()
   123  		if err != nil {
   124  			logger.Error("check write availability error", zap.Error(err))
   125  			xhttp.WriteError(w, err)
   126  			return
   127  		}
   128  		if !ready {
   129  			result.NotReadyWrites = append(result.NotReadyWrites, nsResult)
   130  		} else {
   131  			result.ReadyWrites = append(result.ReadyWrites, nsResult)
   132  		}
   133  	}
   134  
   135  	resp, err := json.Marshal(result)
   136  	if err != nil {
   137  		xhttp.WriteError(w, err)
   138  		return
   139  	}
   140  
   141  	if n := len(result.NotReadyReads); req.reads && n > 0 {
   142  		err := fmt.Errorf("not ready namespaces for read: %d", n)
   143  		xhttp.WriteError(w, err, xhttp.WithErrorResponse(resp))
   144  		return
   145  	}
   146  
   147  	if n := len(result.NotReadyWrites); req.writes && n > 0 {
   148  		err := fmt.Errorf("not ready namespaces for write: %d", n)
   149  		xhttp.WriteError(w, err, xhttp.WithErrorResponse(resp))
   150  		return
   151  	}
   152  
   153  	xhttp.WriteJSONResponse(w, result, logger)
   154  }
   155  
   156  type readyRequest struct {
   157  	reads  bool
   158  	writes bool
   159  }
   160  
   161  func (h *ReadyHandler) parseReadyRequestChecked(r *http.Request) (readyRequest, error) {
   162  	result, err := h.parseReadyRequest(r)
   163  	if err != nil {
   164  		// All request parsing errors should be treated as invalid params err.
   165  		return readyRequest{}, xerrors.NewInvalidParamsError(err)
   166  	}
   167  	return result, nil
   168  }
   169  
   170  func (h *ReadyHandler) parseReadyRequest(r *http.Request) (readyRequest, error) {
   171  	// Default to checking for both read and write availability.
   172  	var (
   173  		req = readyRequest{
   174  			reads:  true,
   175  			writes: true,
   176  		}
   177  		err error
   178  	)
   179  	if str := r.URL.Query().Get("reads"); str != "" {
   180  		req.reads, err = strconv.ParseBool(str)
   181  		if err != nil {
   182  			return readyRequest{}, xerrors.NewInvalidParamsError(err)
   183  		}
   184  	}
   185  	if str := r.URL.Query().Get("writes"); str != "" {
   186  		req.writes, err = strconv.ParseBool(str)
   187  		if err != nil {
   188  			return readyRequest{}, xerrors.NewInvalidParamsError(err)
   189  		}
   190  	}
   191  	return req, nil
   192  }