func InitStaticRouter(staticDir, staticETag string, mgr *cbgt.Manager) *mux.Router { prefix := "" if mgr != nil { prefix = mgr.Options()["urlPrefix"] } hfsStaticX := http.FileServer(assetFS()) router := mux.NewRouter() router.StrictSlash(true) router.Handle(prefix+"/", http.RedirectHandler(prefix+"/index.html", 302)) router.Handle(prefix+"/index.html", http.RedirectHandler(prefix+"/staticx/index.html", 302)) router.Handle(prefix+"/static/partials/index/start.html", http.RedirectHandler(prefix+"/staticx/partials/index/start.html", 302)) router = rest.InitStaticRouterEx(router, staticDir, staticETag, []string{ prefix + "/indexes", prefix + "/nodes", prefix + "/monitor", prefix + "/manage", prefix + "/logs", prefix + "/debug", }, http.RedirectHandler(prefix+"/staticx/index.html", 302), mgr) router.PathPrefix(prefix + "/staticx/").Handler( http.StripPrefix(prefix+"/staticx/", hfsStaticX)) return router }
func BlevePIndexImplInitRouter(r *mux.Router, phase string, mgr *cbgt.Manager) { prefix := "" if mgr != nil { prefix = mgr.Options()["urlPrefix"] } if phase == "static.before" { staticBleveMapping := http.FileServer(bleveMappingUI.AssetFS()) r.PathPrefix(prefix + "/static-bleve-mapping/").Handler( http.StripPrefix(prefix+"/static-bleve-mapping/", staticBleveMapping)) bleveMappingUI.RegisterHandlers(r, prefix+"/api") } if phase == "manager.after" { // Using standard bleveHttp handlers for /api/pindex-bleve endpoints. // listIndexesHandler := bleveHttp.NewListIndexesHandler() r.Handle(prefix+"/api/pindex-bleve", listIndexesHandler).Methods("GET") getIndexHandler := bleveHttp.NewGetIndexHandler() getIndexHandler.IndexNameLookup = rest.PIndexNameLookup r.Handle(prefix+"/api/pindex-bleve/{pindexName}", getIndexHandler).Methods("GET") docCountHandler := bleveHttp.NewDocCountHandler("") docCountHandler.IndexNameLookup = rest.PIndexNameLookup r.Handle(prefix+"/api/pindex-bleve/{pindexName}/count", docCountHandler).Methods("GET") searchHandler := bleveHttp.NewSearchHandler("") searchHandler.IndexNameLookup = rest.PIndexNameLookup r.Handle(prefix+"/api/pindex-bleve/{pindexName}/query", searchHandler).Methods("POST") docGetHandler := bleveHttp.NewDocGetHandler("") docGetHandler.IndexNameLookup = rest.PIndexNameLookup docGetHandler.DocIDLookup = rest.DocIDLookup r.Handle(prefix+"/api/pindex-bleve/{pindexName}/doc/{docID}", docGetHandler).Methods("GET") debugDocHandler := bleveHttp.NewDebugDocumentHandler("") debugDocHandler.IndexNameLookup = rest.PIndexNameLookup debugDocHandler.DocIDLookup = rest.DocIDLookup r.Handle(prefix+"/api/pindex-bleve/{pindexName}/docDebug/{docID}", debugDocHandler).Methods("GET") listFieldsHandler := bleveHttp.NewListFieldsHandler("") listFieldsHandler.IndexNameLookup = rest.PIndexNameLookup r.Handle(prefix+"/api/pindex-bleve/{pindexName}/fields", listFieldsHandler).Methods("GET") } }
// InitStaticRouterEx is like InitStaticRouter, but with optional // manager parameter for more options. func InitStaticRouterEx(r *mux.Router, staticDir, staticETag string, pages []string, pagesHandler http.Handler, mgr *cbgt.Manager) *mux.Router { prefix := "" if mgr != nil { prefix = mgr.Options()["urlPrefix"] } PIndexTypesInitRouter(r, "static.before", mgr) var s http.FileSystem if staticDir != "" { if _, err := os.Stat(staticDir); err == nil { log.Printf("http: serving assets from staticDir: %s", staticDir) s = http.Dir(staticDir) } } if s == nil { log.Printf("http: serving assets from embedded data") s = AssetFS() } r.PathPrefix(prefix + "/static/").Handler( http.StripPrefix(prefix+"/static/", ETagFileHandler{http.FileServer(s), staticETag})) // Bootstrap UI insists on loading templates from this path. r.PathPrefix(prefix + "/template/").Handler( http.StripPrefix(prefix+"/template/", ETagFileHandler{http.FileServer(s), staticETag})) // If client ask for any of the pages, redirect. for _, p := range pages { if pagesHandler != nil { r.PathPrefix(p).Handler(pagesHandler) } else { r.PathPrefix(p).Handler(RewriteURL("/", http.FileServer(s))) } } r.Handle(prefix+"/index.html", http.RedirectHandler(prefix+"/static/index.html", 302)) r.Handle(prefix+"/", http.RedirectHandler(prefix+"/static/index.html", 302)) PIndexTypesInitRouter(r, "static.after", mgr) return r }
func NewQueryHandler(mgr *cbgt.Manager) *QueryHandler { slowQueryLogTimeout := time.Duration(0) slowQueryLogTimeoutV := mgr.Options()["slowQueryLogTimeout"] if slowQueryLogTimeoutV != "" { d, err := time.ParseDuration(slowQueryLogTimeoutV) if err == nil { slowQueryLogTimeout = d } } return &QueryHandler{ mgr: mgr, slowQueryLogTimeout: slowQueryLogTimeout, } }
func CheckAPIAuth(mgr *cbgt.Manager, w http.ResponseWriter, req *http.Request, path string) (allowed bool) { authType := "" if mgr != nil && mgr.Options() != nil { authType = mgr.Options()["authType"] } if authType == "" { return true } if authType != "cbauth" { return false } creds, err := cbauth.AuthWebCreds(req) if err != nil { http.Error(w, fmt.Sprintf("rest_auth: cbauth.AuthWebCreds,"+ " err: %v ", err), 403) return false } perms, err := preparePerms(mgr, req, req.Method, path) if err != nil { http.Error(w, fmt.Sprintf("rest_auth: preparePerm,"+ " err: %v ", err), 403) return false } for _, perm := range perms { allowed, err = creds.IsAllowed(perm) if err != nil { http.Error(w, fmt.Sprintf("rest_auth: cbauth.IsAllowed,"+ " err: %v ", err), 403) return false } if !allowed { cbauth.SendForbidden(w, perm) return false } } return true }
func bleveIndexTargets(mgr *cbgt.Manager, indexName, indexUUID string, ensureCanRead bool, consistencyParams *cbgt.ConsistencyParams, cancelCh <-chan bool, collector BleveIndexCollector) error { planPIndexNodeFilter := cbgt.PlanPIndexNodeOk if ensureCanRead { planPIndexNodeFilter = cbgt.PlanPIndexNodeCanRead } localPIndexes, remotePlanPIndexes, err := mgr.CoveringPIndexes(indexName, indexUUID, planPIndexNodeFilter, "queries") if err != nil { return fmt.Errorf("bleve: bleveIndexTargets, err: %v", err) } prefix := mgr.Options()["urlPrefix"] for _, remotePlanPIndex := range remotePlanPIndexes { baseURL := "http://" + remotePlanPIndex.NodeDef.HostPort + prefix + "/api/pindex/" + remotePlanPIndex.PlanPIndex.Name collector.Add(&IndexClient{ name: fmt.Sprintf("IndexClient - %s", baseURL), QueryURL: baseURL + "/query", CountURL: baseURL + "/count", Consistency: consistencyParams, // TODO: Propagate auth to remote client. }) } // TODO: Should kickoff remote queries concurrently before we wait. return cbgt.ConsistencyWaitGroup(indexName, consistencyParams, cancelCh, localPIndexes, func(localPIndex *cbgt.PIndex) error { bindex, ok := localPIndex.Impl.(bleve.Index) if !ok || bindex == nil || !strings.HasPrefix(localPIndex.IndexType, "fulltext-index") { return fmt.Errorf("bleve: wrong type, localPIndex: %#v", localPIndex) } collector.Add(bindex) return nil }) }
// NewRESTRouter creates a mux.Router initialized with the REST API // and web UI routes. See also InitStaticRouter and InitRESTRouter if // you need finer control of the router initialization. func NewRESTRouter(versionMain string, mgr *cbgt.Manager, staticDir, staticETag string, mr *cbgt.MsgRing, assetDir func(name string) ([]string, error), asset func(name string) ([]byte, error)) ( *mux.Router, map[string]RESTMeta, error) { prefix := mgr.Options()["urlPrefix"] r := mux.NewRouter() r.StrictSlash(true) r = InitStaticRouterEx(r, staticDir, staticETag, []string{ prefix + "/indexes", prefix + "/nodes", prefix + "/monitor", prefix + "/manage", prefix + "/logs", prefix + "/debug", }, nil, mgr) return InitRESTRouter(r, versionMain, mgr, staticDir, staticETag, mr, assetDir, asset) }
func InitStaticRouter(staticDir, staticETag string, mgr *cbgt.Manager) *mux.Router { router := mux.NewRouter() router.StrictSlash(true) showUI := true if mgr != nil && mgr.Options()["hideUI"] != "" { hideUI, err := strconv.ParseBool(mgr.Options()["hideUI"]) if err == nil && hideUI { showUI = false } } if showUI { prefix := "" if mgr != nil { prefix = mgr.Options()["urlPrefix"] } hfsStaticX := http.FileServer(assetFS()) router.Handle(prefix+"/", http.RedirectHandler(prefix+"/index.html", 302)) router.Handle(prefix+"/index.html", http.RedirectHandler(prefix+"/staticx/index.html", 302)) router.Handle(prefix+"/static/partials/index/start.html", http.RedirectHandler(prefix+"/staticx/partials/index/start.html", 302)) router = rest.InitStaticRouterEx(router, staticDir, staticETag, []string{ prefix + "/indexes", prefix + "/nodes", prefix + "/monitor", prefix + "/manage", prefix + "/logs", prefix + "/debug", }, http.RedirectHandler(prefix+"/staticx/index.html", 302), mgr) router.PathPrefix(prefix + "/staticx/").Handler( http.StripPrefix(prefix+"/staticx/", hfsStaticX)) } return router }
func QueryBlevePIndexImpl(mgr *cbgt.Manager, indexName, indexUUID string, req []byte, res io.Writer) error { queryCtlParams := cbgt.QueryCtlParams{ Ctl: cbgt.QueryCtl{ Timeout: cbgt.QUERY_CTL_DEFAULT_TIMEOUT_MS, }, } err := json.Unmarshal(req, &queryCtlParams) if err != nil { return fmt.Errorf("bleve: QueryBlevePIndexImpl"+ " parsing queryCtlParams, req: %s, err: %v", req, err) } searchRequest := &bleve.SearchRequest{} err = json.Unmarshal(req, searchRequest) if err != nil { return fmt.Errorf("bleve: QueryBlevePIndexImpl"+ " parsing searchRequest, req: %s, err: %v", req, err) } err = searchRequest.Query.Validate() if err != nil { return err } v, exists := mgr.Options()["bleveMaxResultWindow"] if exists { bleveMaxResultWindow, err := strconv.Atoi(v) if err != nil { return err } if searchRequest.From+searchRequest.Size > bleveMaxResultWindow { return fmt.Errorf("bleve: bleveMaxResultWindow exceeded,"+ " from: %d, size: %d, bleveMaxResultWindow: %d", searchRequest.From, searchRequest.Size, bleveMaxResultWindow) } } cancelCh := cbgt.TimeoutCancelChan(queryCtlParams.Ctl.Timeout) alias, err := bleveIndexAlias(mgr, indexName, indexUUID, true, queryCtlParams.Ctl.Consistency, cancelCh) if err != nil { return err } doneCh := make(chan struct{}) var searchResult *bleve.SearchResult go func() { searchResult, err = alias.Search(searchRequest) close(doneCh) }() select { case <-cancelCh: err = fmt.Errorf("pindex_bleve: query timeout") case <-doneCh: if searchResult != nil { rest.MustEncode(res, searchResult) } } return err }
func initNsServerCaching(mgr *cbgt.Manager) { runSourcePartitionSeqsOnce.Do(func() { go RunSourcePartitionSeqs(mgr.Options(), nil) go RunRecentInfoCache(mgr) }) }
// InitRESTRouter initializes a mux.Router with REST API routes with // extra option. func InitRESTRouterEx(r *mux.Router, versionMain string, mgr *cbgt.Manager, staticDir, staticETag string, mr *cbgt.MsgRing, assetDir func(name string) ([]string, error), asset func(name string) ([]byte, error), options map[string]interface{}) ( *mux.Router, map[string]RESTMeta, error) { var authHandler func(http.Handler) http.Handler mapRESTPathStats := map[string]*RESTPathStats{} // Keyed by path spec. if options != nil { if v, ok := options["auth"]; ok { authHandler, ok = v.(func(http.Handler) http.Handler) if !ok { return nil, nil, fmt.Errorf("rest: auth function invalid") } } if v, ok := options["mapRESTPathStats"]; ok { mapRESTPathStats, ok = v.(map[string]*RESTPathStats) if !ok { return nil, nil, fmt.Errorf("rest: mapRESTPathStats invalid") } } } prefix := mgr.Options()["urlPrefix"] PIndexTypesInitRouter(r, "manager.before", mgr) meta := map[string]RESTMeta{} handle := func(path string, method string, h http.Handler, opts map[string]string) { opts["_path"] = path if a, ok := h.(RESTOpts); ok { a.RESTOpts(opts) } prefixPath := prefix + path restMeta := RESTMeta{prefixPath, method, opts} meta[prefixPath+" "+RESTMethodOrds[method]+method] = restMeta h = &HandlerWithRESTMeta{ h: h, RESTMeta: &restMeta, pathStats: mapRESTPathStats[path], focusName: PathFocusName(path), } if authHandler != nil { h = authHandler(h) } r.Handle(prefixPath, h).Methods(method).Name(prefixPath) } handle("/api/index", "GET", NewListIndexHandler(mgr), map[string]string{ "_category": "Indexing|Index definition", "_about": `Returns all index definitions as JSON.`, "version introduced": "0.0.1", }) handle("/api/index/{indexName}", "PUT", NewCreateIndexHandler(mgr), map[string]string{ "_category": "Indexing|Index definition", "_about": `Creates/updates an index definition.`, "version introduced": "0.0.1", }) handle("/api/index/{indexName}", "DELETE", NewDeleteIndexHandler(mgr), map[string]string{ "_category": "Indexing|Index definition", "_about": `Deletes an index definition.`, "version introduced": "0.0.1", }) handle("/api/index/{indexName}", "GET", NewGetIndexHandler(mgr), map[string]string{ "_category": "Indexing|Index definition", "_about": `Returns the definition of an index as JSON.`, "version introduced": "0.0.1", }) if mgr == nil || mgr.TagsMap() == nil || mgr.TagsMap()["queryer"] { handle("/api/index/{indexName}/count", "GET", NewCountHandler(mgr), map[string]string{ "_category": "Indexing|Index querying", "_about": `Returns the count of indexed documents.`, "version introduced": "0.0.1", }) handle("/api/index/{indexName}/query", "POST", NewQueryHandler(mgr, mapRESTPathStats["/api/index/{indexName}/query"]), map[string]string{ "_category": "Indexing|Index querying", "_about": `Queries an index.`, "version introduced": "0.2.0", }) } handle("/api/index/{indexName}/planFreezeControl/{op}", "POST", NewIndexControlHandler(mgr, "planFreeze", map[string]bool{ "freeze": true, "unfreeze": true, }), map[string]string{ "_category": "Indexing|Index management", "_about": `Freeze the assignment of index partitions to nodes.`, "param: op": "required, string, URL path parameter\n\n" + `Allowed values for op are "freeze" or "unfreeze".`, "version introduced": "0.0.1", }) handle("/api/index/{indexName}/ingestControl/{op}", "POST", NewIndexControlHandler(mgr, "write", map[string]bool{ "pause": true, "resume": true, }), map[string]string{ "_category": "Indexing|Index management", "_about": `Pause index updates and maintenance (no more ingesting document mutations).`, "param: op": "required, string, URL path parameter\n\n" + `Allowed values for op are "pause" or "resume".`, "version introduced": "0.0.1", }) handle("/api/index/{indexName}/queryControl/{op}", "POST", NewIndexControlHandler(mgr, "read", map[string]bool{ "allow": true, "disallow": true, }), map[string]string{ "_category": "Indexing|Index management", "_about": `Disallow queries on an index.`, "param: op": "required, string, URL path parameter\n\n" + `Allowed values for op are "allow" or "disallow".`, "version introduced": "0.0.1", }) if mgr == nil || mgr.TagsMap() == nil || mgr.TagsMap()["pindex"] { handle("/api/pindex", "GET", NewListPIndexHandler(mgr), map[string]string{ "_category": "x/Advanced|x/Index partition definition", "version introduced": "0.0.1", }) handle("/api/pindex/{pindexName}", "GET", NewGetPIndexHandler(mgr), map[string]string{ "_category": "x/Advanced|x/Index partition definition", "version introduced": "0.0.1", }) handle("/api/pindex/{pindexName}/count", "GET", NewCountPIndexHandler(mgr), map[string]string{ "_category": "x/Advanced|x/Index partition querying", "version introduced": "0.0.1", }) handle("/api/pindex/{pindexName}/query", "POST", NewQueryPIndexHandler(mgr), map[string]string{ "_category": "x/Advanced|x/Index partition querying", "version introduced": "0.2.0", }) } handle("/api/managerOptions", "PUT", NewManagerOptions(mgr), map[string]string{ "_category": "Node|Node configuration", "_about": "Set the options for the manager", "version introduced": "4.2.0", }) handle("/api/cfg", "GET", NewCfgGetHandler(mgr), map[string]string{ "_category": "Node|Node configuration", "_about": `Returns the node's current view of the cluster's configuration as JSON.`, "version introduced": "0.0.1", }) handle("/api/cfgRefresh", "POST", NewCfgRefreshHandler(mgr), map[string]string{ "_category": "Node|Node configuration", "_about": `Requests the node to refresh its configuration from the configuration provider.`, "version introduced": "0.0.1", }) handle("/api/log", "GET", NewLogGetHandler(mgr, mr), map[string]string{ "_category": "Node|Node diagnostics", "_about": `Returns recent log messages and key events for the node as JSON.`, "version introduced": "0.0.1", }) handle("/api/manager", "GET", NewManagerHandler(mgr), map[string]string{ "_category": "Node|Node configuration", "_about": `Returns runtime config information about this node.`, "version introduced": "0.4.0", }) handle("/api/managerKick", "POST", NewManagerKickHandler(mgr), map[string]string{ "_category": "Node|Node configuration", "_about": `Forces the node to replan resource assignments (by running the planner, if enabled) and to update its runtime state to reflect the latest plan (by running the janitor, if enabled).`, "version introduced": "0.0.1", }) handle("/api/managerMeta", "GET", NewManagerMetaHandler(mgr, meta), map[string]string{ "_category": "Node|Node configuration", "_about": `Returns information on the node's capabilities, including available indexing and storage options as JSON, and is intended to help management tools and web UI's to be more dynamically metadata driven.`, "version introduced": "0.0.1", }) handle("/api/runtime", "GET", NewRuntimeGetHandler(versionMain, mgr), map[string]string{ "_category": "Node|Node diagnostics", "_about": `Returns information on the node's software, such as version strings and slow-changing runtime settings as JSON.`, "version introduced": "0.0.1", }) handle("/api/runtime/args", "GET", http.HandlerFunc(RESTGetRuntimeArgs), map[string]string{ "_category": "Node|Node diagnostics", "_about": `Returns information on the node's command-line, parameters, environment variables and O/S process values as JSON.`, "version introduced": "0.0.1", }) handle("/api/diag", "GET", NewDiagGetHandler(versionMain, mgr, mr, assetDir, asset), map[string]string{ "_category": "Node|Node diagnostics", "_about": `Returns full set of diagnostic information from the node in one shot as JSON. That is, the /api/diag response will be the union of the responses from the other REST API diagnostic and monitoring endpoints from the node, and is intended to make production support easier.`, "version introduced": "0.0.1", }) handle("/api/ping", "GET", &NoopHandler{}, map[string]string{ "_category": "Node|Node diagnostics", "_about": `Returns an empty body as a quick aliveness check.`, "version introduced": "5.0.0", }) handle("/api/runtime/gc", "POST", http.HandlerFunc(RESTPostRuntimeGC), map[string]string{ "_category": "Node|Node management", "_about": `Requests the node to perform a GC.`, "version introduced": "0.0.1", }) handle("/api/runtime/profile/cpu", "POST", http.HandlerFunc(RESTProfileCPU), map[string]string{ "_category": "Node|Node diagnostics", "_about": `Requests the node to capture local cpu usage profiling information.`, "version introduced": "0.0.1", }) handle("/api/runtime/profile/memory", "POST", http.HandlerFunc(RESTProfileMemory), map[string]string{ "_category": "Node|Node diagnostics", "_about": `Requests the node to capture lcoal memory usage profiling information.`, "version introduced": "0.0.1", }) handle("/api/runtime/stats", "GET", http.HandlerFunc(RESTGetRuntimeStats), map[string]string{ "_category": "Node|Node monitoring", "_about": `Returns information on the node's low-level runtime stats as JSON.`, "version introduced": "0.0.1", }) handle("/api/runtime/statsMem", "GET", http.HandlerFunc(RESTGetRuntimeStatsMem), map[string]string{ "_category": "Node|Node monitoring", "_about": `Returns information on the node's low-level GC and memory related runtime stats as JSON.`, "version introduced": "0.0.1", }) handle("/api/stats", "GET", NewStatsHandler(mgr), map[string]string{ "_category": "Indexing|Index monitoring", "_about": `Returns indexing and data related metrics, timings and counters from the node as JSON.`, "version introduced": "0.0.1", }) // TODO: If we ever implement cluster-wide index stats, we should // have it under /api/index/{indexName}/stats GET endpoint. // handle("/api/stats/index/{indexName}", "GET", NewStatsHandler(mgr), map[string]string{ "_category": "Indexing|Index monitoring", "_about": `Returns metrics, timings and counters for a single index from the node as JSON.`, "version introduced": "0.0.1", }) handle("/api/stats/sourceStats/{indexName}", "GET", NewSourceStatsHandler(mgr), map[string]string{ "_category": "Indexing|Index monitoring", "_about": `Returns data source specific stats for an index as JSON.`, "version introduced": "4.2.0", }) handle("/api/stats/sourcePartitionSeqs/{indexName}", "GET", NewSourcePartitionSeqsHandler(mgr), map[string]string{ "_category": "Indexing|Index monitoring", "_about": `Returns data source partiton seqs for an index as JSON.`, "version introduced": "4.2.0", }) PIndexTypesInitRouter(r, "manager.after", mgr) return r, meta, nil }