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 }