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 }