From 6c3f3ac0f092340338604863d19388acf9bb8a98 Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Thu, 1 Mar 2018 17:39:14 +0300 Subject: [PATCH 01/12] Added API struct; moved query logic to API --- api.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ handler.go | 47 ++++--------------------- server.go | 5 +++ 3 files changed, 113 insertions(+), 40 deletions(-) create mode 100644 api.go diff --git a/api.go b/api.go new file mode 100644 index 000000000..5ab4a36aa --- /dev/null +++ b/api.go @@ -0,0 +1,101 @@ +// Copyright 2017 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pilosa + +import ( + "context" + "strings" + + "github.com/pilosa/pilosa/pql" +) + +type QueryOptions struct { + Remote bool + ExcludeAttrs bool + ExcludeBits bool + ColumnAttrs bool +} + +type API struct { + holder *Holder + // The execution engine for running queries. + executor interface { + Execute(context context.Context, index string, query *pql.Query, slices []uint64, opt *ExecOptions) ([]interface{}, error) + } +} + +func (a *API) ExecuteQuery(ctx context.Context, req *QueryRequest) (QueryResponse, error) { + resp := QueryResponse{} + + q, err := pql.NewParser(strings.NewReader(req.Query)).Parse() + if err != nil { + // TODO: Wrap + return resp, err + } + execOpts := &ExecOptions{ + Remote: req.Remote, + ExcludeAttrs: req.ExcludeAttrs, + ExcludeBits: req.ExcludeBits, + } + results, err := a.executor.Execute(ctx, req.Index, q, req.Slices, execOpts) + if err != nil { + return resp, err + } + resp.Results = results + + // Fill column attributes if requested. + if req.ColumnAttrs && !req.ExcludeBits { + // Consolidate all column ids across all calls. + var columnIDs []uint64 + for _, result := range results { + bm, ok := result.(*Bitmap) + if !ok { + continue + } + columnIDs = uint64Slice(columnIDs).merge(bm.Bits()) + } + + // Retrieve column attributes across all calls. + columnAttrSets, err := a.readColumnAttrSets(a.holder.Index(req.Index), columnIDs) + if err != nil { + return resp, err + } + resp.ColumnAttrSets = columnAttrSets + } + return resp, nil +} + +// readColumnAttrSets returns a list of column attribute objects by id. +func (api *API) readColumnAttrSets(index *Index, ids []uint64) ([]*ColumnAttrSet, error) { + if index == nil { + return nil, nil + } + + ax := make([]*ColumnAttrSet, 0, len(ids)) + for _, id := range ids { + // Read attributes for column. Skip column if empty. + attrs, err := index.ColumnAttrStore().Attrs(id) + if err != nil { + return nil, err + } else if len(attrs) == 0 { + continue + } + + // Append column with attributes. + ax = append(ax, &ColumnAttrSet{ID: id, Attrs: attrs}) + } + + return ax, nil +} diff --git a/handler.go b/handler.go index ffd28da46..c40148b43 100644 --- a/handler.go +++ b/handler.go @@ -69,6 +69,8 @@ type Handler struct { // Keeps the query argument validators for each handler validators map[string]*queryValidationSpec + + api *API } // externalPrefixFlag denotes endpoints that are intended to be exposed to clients. @@ -328,8 +330,6 @@ type getStatusResponse struct { // handlePostQuery handles /query requests. func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { - indexName := mux.Vars(r)["index"] - // Parse incoming request. req, err := h.readQueryRequest(r) if err != nil { @@ -337,46 +337,13 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { h.writeQueryResponse(w, r, &QueryResponse{Err: err}) return } + // TODO: Remove + req.Index = mux.Vars(r)["index"] - // Build execution options. - opt := &ExecOptions{ - Remote: req.Remote, - ExcludeAttrs: req.ExcludeAttrs, - ExcludeBits: req.ExcludeBits, - } - - // Parse query string. - q, err := pql.NewParser(strings.NewReader(req.Query)).Parse() + resp, err := h.api.ExecuteQuery(r.Context(), req) if err != nil { w.WriteHeader(http.StatusBadRequest) - h.writeQueryResponse(w, r, &QueryResponse{Err: err}) - return - } - - // Execute the query. - results, err := h.Executor.Execute(r.Context(), indexName, q, req.Slices, opt) - resp := &QueryResponse{Results: results, Err: err} - - // Fill column attributes if requested. - if req.ColumnAttrs && !req.ExcludeBits { - // Consolidate all column ids across all calls. - var columnIDs []uint64 - for _, result := range results { - bm, ok := result.(*Bitmap) - if !ok { - continue - } - columnIDs = uint64Slice(columnIDs).merge(bm.Bits()) - } - - // Retrieve column attributes across all calls. - columnAttrSets, err := h.readColumnAttrSets(h.Holder.Index(indexName), columnIDs) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - h.writeQueryResponse(w, r, &QueryResponse{Err: err}) - return - } - resp.ColumnAttrSets = columnAttrSets + h.writeQueryResponse(w, r, &resp) } // Set appropriate status code, if there is an error. @@ -390,7 +357,7 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { } // Write response back to client. - if err := h.writeQueryResponse(w, r, resp); err != nil { + if err := h.writeQueryResponse(w, r, &resp); err != nil { h.Logger.Printf("write query response error: %s", err) } } diff --git a/server.go b/server.go index 08923f13a..ede265e19 100644 --- a/server.go +++ b/server.go @@ -117,6 +117,10 @@ func NewServer() *Server { s.Handler.Holder = s.Holder s.diagnostics.server = s + s.Handler.api = &API{ + holder: s.Holder, + } + return s } @@ -172,6 +176,7 @@ func (s *Server) Open() error { s.Handler.Executor = e s.Cluster.prefect = s.Handler + s.Handler.api.executor = e // Initialize Holder. s.Holder.Broadcaster = s.Broadcaster From 2ebc1914be0865b074520cda3bfd0a21e897d8c7 Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Fri, 2 Mar 2018 18:40:50 +0300 Subject: [PATCH 02/12] More API functions --- api.go | 133 ++++++++++++++++++++++++++++++++++++++++++++++++----- handler.go | 112 +++++++++++--------------------------------- server.go | 8 ++-- 3 files changed, 150 insertions(+), 103 deletions(-) diff --git a/api.go b/api.go index 5ab4a36aa..e004821fb 100644 --- a/api.go +++ b/api.go @@ -16,23 +16,31 @@ package pilosa import ( "context" + "fmt" + "io/ioutil" + "log" "strings" + "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" ) -type QueryOptions struct { - Remote bool - ExcludeAttrs bool - ExcludeBits bool - ColumnAttrs bool +type API struct { + Holder *Holder + // The execution engine for running queries. + Executor interface { + Execute(context context.Context, index string, query *pql.Query, slices []uint64, opt *ExecOptions) ([]interface{}, error) + } + Broadcaster Broadcaster + logger *log.Logger } -type API struct { - holder *Holder - // The execution engine for running queries. - executor interface { - Execute(context context.Context, index string, query *pql.Query, slices []uint64, opt *ExecOptions) ([]interface{}, error) +func NewAPI(logger *log.Logger) *API { + if logger == nil { + logger = log.New(ioutil.Discard, "", 0) + } + return &API{ + logger: logger, } } @@ -49,7 +57,7 @@ func (a *API) ExecuteQuery(ctx context.Context, req *QueryRequest) (QueryRespons ExcludeAttrs: req.ExcludeAttrs, ExcludeBits: req.ExcludeBits, } - results, err := a.executor.Execute(ctx, req.Index, q, req.Slices, execOpts) + results, err := a.Executor.Execute(ctx, req.Index, q, req.Slices, execOpts) if err != nil { return resp, err } @@ -68,7 +76,7 @@ func (a *API) ExecuteQuery(ctx context.Context, req *QueryRequest) (QueryRespons } // Retrieve column attributes across all calls. - columnAttrSets, err := a.readColumnAttrSets(a.holder.Index(req.Index), columnIDs) + columnAttrSets, err := a.readColumnAttrSets(a.Holder.Index(req.Index), columnIDs) if err != nil { return resp, err } @@ -99,3 +107,104 @@ func (api *API) readColumnAttrSets(index *Index, ids []uint64) ([]*ColumnAttrSet return ax, nil } + +func (api *API) CreateIndex(ctx context.Context, indexName string, options IndexOptions) (*Index, error) { + // Create index. + index, err := api.Holder.CreateIndex(indexName, options) + if err != nil { + return nil, err + } + // Send the create index message to all nodes. + err = api.Broadcaster.SendSync( + &internal.CreateIndexMessage{ + Index: indexName, + Meta: options.Encode(), + }) + if err != nil { + api.logger.Printf("problem sending CreateIndex message: %s", err) + return nil, err + } + api.Holder.Stats.Count("createIndex", 1, 1.0) + return index, nil +} + +func (api *API) ReadIndex(ctx context.Context, indexName string) (*Index, error) { + index := api.Holder.Index(indexName) + if index == nil { + return nil, ErrIndexNotFound + } + return index, nil +} + +func (api *API) DeleteIndex(ctx context.Context, indexName string) error { + // Delete index from the holder. + err := api.Holder.DeleteIndex(indexName) + if err != nil { + return err + } + // Send the delete index message to all nodes. + err = api.Broadcaster.SendSync( + &internal.DeleteIndexMessage{ + Index: indexName, + }) + if err != nil { + api.logger.Printf("problem sending DeleteIndex message: %s", err) + return err + } + api.Holder.Stats.Count("deleteIndex", 1, 1.0) + return nil +} + +func (api *API) CreateFrame(ctx context.Context, indexName string, frameName string, options FrameOptions) (*Frame, error) { + // Find index. + index := api.Holder.Index(indexName) + if index == nil { + return nil, ErrIndexNotFound + } + + // Create frame. + frame, err := index.CreateFrame(frameName, options) + if err != nil { + return nil, err + } + + // Send the create frame message to all nodes. + err = api.Broadcaster.SendSync( + &internal.CreateFrameMessage{ + Index: indexName, + Frame: frameName, + Meta: options.Encode(), + }) + if err != nil { + api.logger.Printf("problem sending CreateFrame message: %s", err) + return nil, err + } + api.Holder.Stats.CountWithCustomTags("createFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) + return frame, nil +} + +func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName string) error { + // Find index. + index := api.Holder.Index(indexName) + if index == nil { + return ErrIndexNotFound + } + + // Delete frame from the index. + if err := index.DeleteFrame(frameName); err != nil { + return err + } + + // Send the delete frame message to all nodes. + err := api.Broadcaster.SendSync( + &internal.DeleteFrameMessage{ + Index: indexName, + Frame: frameName, + }) + if err != nil { + api.logger.Printf("problem sending DeleteFrame message: %s", err) + return err + } + api.Holder.Stats.CountWithCustomTags("deleteFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) + return nil +} diff --git a/handler.go b/handler.go index c40148b43..bd47524ed 100644 --- a/handler.go +++ b/handler.go @@ -70,7 +70,7 @@ type Handler struct { // Keeps the query argument validators for each handler validators map[string]*queryValidationSpec - api *API + API *API } // externalPrefixFlag denotes endpoints that are intended to be exposed to clients. @@ -340,7 +340,7 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { // TODO: Remove req.Index = mux.Vars(r)["index"] - resp, err := h.api.ExecuteQuery(r.Context(), req) + resp, err := h.API.ExecuteQuery(r.Context(), req) if err != nil { w.WriteHeader(http.StatusBadRequest) h.writeQueryResponse(w, r, &resp) @@ -385,9 +385,9 @@ func (h *Handler) handleGetIndexes(w http.ResponseWriter, r *http.Request) { // handleGetIndex handles GET /index/ requests. func (h *Handler) handleGetIndex(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) + index, err := h.API.ReadIndex(r.Context(), indexName) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) return } @@ -469,28 +469,17 @@ type postIndexResponse struct{} // handleDeleteIndex handles DELETE /index request. func (h *Handler) handleDeleteIndex(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] - - // Delete index from the holder. - if err := h.Holder.DeleteIndex(indexName); err != nil { + err := h.API.DeleteIndex(r.Context(), indexName) + if err != nil { + h.Logger.Printf("problem deleting index: %s", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Send the delete index message to all nodes. - err := h.Broadcaster.SendSync( - &internal.DeleteIndexMessage{ - Index: indexName, - }) - if err != nil { - h.Logger.Printf("problem sending DeleteIndex message: %s", err) - } - // Encode response. if err := json.NewEncoder(w).Encode(deleteIndexResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) } - - h.Holder.Stats.Count("deleteIndex", 1, 1.0) } type deleteIndexResponse struct{} @@ -510,8 +499,7 @@ func (h *Handler) handlePostIndex(w http.ResponseWriter, r *http.Request) { return } - // Create index. - _, err = h.Holder.CreateIndex(indexName, req.Options) + _, err = h.API.CreateIndex(r.Context(), indexName, req.Options) if err == ErrIndexExists { http.Error(w, err.Error(), http.StatusConflict) return @@ -520,24 +508,10 @@ func (h *Handler) handlePostIndex(w http.ResponseWriter, r *http.Request) { return } - // Send the create index message to all nodes. - err = h.Broadcaster.SendSync( - &internal.CreateIndexMessage{ - Index: indexName, - Meta: req.Options.Encode(), - }) - if err != nil { - h.Logger.Printf("problem sending CreateIndex message: %s", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - // Encode response. if err := json.NewEncoder(w).Encode(postIndexResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) } - - h.Holder.Stats.Count("createIndex", 1, 1.0) } // handlePatchIndexTimeQuantum handles PATCH /index/time_quantum request. @@ -655,41 +629,22 @@ func (h *Handler) handlePostFrame(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } - - // Find index. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - // Create frame. - _, err = index.CreateFrame(frameName, req.Options) - if err == ErrFrameExists { - http.Error(w, err.Error(), http.StatusConflict) - return - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Send the create frame message to all nodes. - err = h.Broadcaster.SendSync( - &internal.CreateFrameMessage{ - Index: indexName, - Frame: frameName, - Meta: req.Options.Encode(), - }) + _, err = h.API.CreateFrame(r.Context(), indexName, frameName, req.Options) if err != nil { - h.Logger.Printf("problem sending CreateFrame message: %s", err) + switch err { + case ErrIndexNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + case ErrFrameExists: + http.Error(w, err.Error(), http.StatusConflict) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return } - // Encode response. if err := json.NewEncoder(w).Encode(postFrameResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) } - - h.Holder.Stats.CountWithCustomTags("createFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) } type _postFrameRequest postFrameRequest @@ -742,37 +697,22 @@ func (h *Handler) handleDeleteFrame(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] frameName := mux.Vars(r)["frame"] - // Find index. - index := h.Holder.Index(indexName) - if index == nil { - if err := json.NewEncoder(w).Encode(deleteIndexResponse{}); err != nil { - h.Logger.Printf("response encoding error: %s", err) + err := h.API.DeleteFrame(r.Context(), indexName, frameName) + if err != nil { + if err == ErrIndexNotFound { + if err := json.NewEncoder(w).Encode(deleteIndexResponse{}); err != nil { + h.Logger.Printf("response encoding error: %s", err) + } + return } - return - } - - // Delete frame from the index. - if err := index.DeleteFrame(frameName); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Send the delete frame message to all nodes. - err := h.Broadcaster.SendSync( - &internal.DeleteFrameMessage{ - Index: indexName, - Frame: frameName, - }) - if err != nil { - h.Logger.Printf("problem sending DeleteFrame message: %s", err) - } - // Encode response. if err := json.NewEncoder(w).Encode(deleteFrameResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) } - - h.Holder.Stats.CountWithCustomTags("deleteFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) } type deleteFrameResponse struct{} diff --git a/server.go b/server.go index ede265e19..1973f746e 100644 --- a/server.go +++ b/server.go @@ -117,10 +117,7 @@ func NewServer() *Server { s.Handler.Holder = s.Holder s.diagnostics.server = s - s.Handler.api = &API{ - holder: s.Holder, - } - + s.Handler.API = NewAPI(s.logger) return s } @@ -169,6 +166,7 @@ func (s *Server) Open() error { // Initialize HTTP handler. s.Handler.Broadcaster = s.Broadcaster + s.Handler.API.Broadcaster = s.Broadcaster s.Handler.BroadcastHandler = s s.Handler.StatusHandler = s s.Handler.Node = node @@ -176,7 +174,7 @@ func (s *Server) Open() error { s.Handler.Executor = e s.Cluster.prefect = s.Handler - s.Handler.api.executor = e + s.Handler.API.Executor = e // Initialize Holder. s.Holder.Broadcaster = s.Broadcaster From b702f70610962341116d9678ce7c5877b7e171ca Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Mon, 5 Mar 2018 18:28:24 +0300 Subject: [PATCH 03/12] Moved more of handler to API --- api.go | 464 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 462 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index e004821fb..e544d2d3f 100644 --- a/api.go +++ b/api.go @@ -16,11 +16,17 @@ package pilosa import ( "context" + "encoding/csv" "fmt" + "io" "io/ioutil" "log" + "net/http" + "reflect" + "strconv" "strings" + "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" ) @@ -31,8 +37,13 @@ type API struct { Executor interface { Execute(context context.Context, index string, query *pql.Query, slices []uint64, opt *ExecOptions) ([]interface{}, error) } - Broadcaster Broadcaster - logger *log.Logger + Broadcaster Broadcaster + BroadcastHandler BroadcastHandler + StatusHandler StatusHandler + Cluster *Cluster + URI *URI + RemoteClient *http.Client + logger *log.Logger } func NewAPI(logger *log.Logger) *API { @@ -208,3 +219,452 @@ func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName str api.Holder.Stats.CountWithCustomTags("deleteFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) return nil } + +func (api *API) ExportCSV(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, w io.Writer) error { + // Find the fragment. + f := api.Holder.Fragment(indexName, frameName, viewName, slice) + if f == nil { + return ErrFragmentNotFound + } + + // Wrap writer with a CSV writer. + cw := csv.NewWriter(w) + + // Iterate over each bit. + if err := f.ForEachBit(func(rowID, columnID uint64) error { + return cw.Write([]string{ + strconv.FormatUint(rowID, 10), + strconv.FormatUint(columnID, 10), + }) + }); err != nil { + return err + } + + // Ensure data is flushed. + cw.Flush() + + return nil +} + +func (api *API) FragmentNodes(ctx context.Context, indexName string, slice uint64) []*Node { + return api.Cluster.FragmentNodes(indexName, slice) +} + +func (api *API) FragmentData(ctx context.Context, indexName string, frameName string, viewName string, slice uint64) (*Fragment, error) { + // Retrieve fragment from holder. + f := api.Holder.Fragment(indexName, frameName, viewName, slice) + if f == nil { + return nil, ErrFragmentNotFound + } + return f, nil +} + +func (api *API) WriteFragmentData(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, reader io.ReadCloser) error { + // Retrieve frame. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return ErrFrameNotFound + } + + // Retrieve view. + view, err := f.CreateViewIfNotExists(viewName) + if err != nil { + return err + } + + // Retrieve fragment from frame. + frag, err := view.CreateFragmentIfNotExists(slice) + if err != nil { + return err + } + + // Read fragment in from request body. + if _, err := frag.ReadFrom(reader); err != nil { + return err + } + return nil +} + +func (api *API) FragmentBlockData(ctx context.Context, req internal.BlockDataRequest) (internal.BlockDataResponse, error) { + // Retrieve fragment from holder. + f := api.Holder.Fragment(req.Index, req.Frame, req.View, req.Slice) + if f == nil { + return internal.BlockDataResponse{}, ErrFragmentNotFound + } + + // Read data + var resp internal.BlockDataResponse + resp.RowIDs, resp.ColumnIDs = f.BlockData(int(req.Block)) + return resp, nil +} + +func (api *API) FragmentBlocks(ctx context.Context, indexName string, frameName string, viewName string, slice uint64) ([]FragmentBlock, error) { + // Retrieve fragment from holder. + f := api.Holder.Fragment(indexName, frameName, viewName, slice) + if f == nil { + return nil, ErrFragmentNotFound + } + + // Retrieve blocks. + blocks := f.Blocks() + return blocks, nil +} + +func (api *API) RestoreFrame(ctx context.Context, indexName string, frameName string, host *URI) error { + // Create a client for the remote cluster. + client := NewInternalHTTPClientFromURI(host, api.RemoteClient) + + // Determine the maximum number of slices. + maxSlices, err := client.MaxSliceByIndex(ctx) + if err != nil { + return err + } + + // Retrieve frame. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return ErrFrameNotFound + } + + // Retrieve list of all views. + views, err := client.FrameViews(ctx, indexName, frameName) + if err != nil { + return err + } + + // Loop over each slice and import it if this node owns it. + for slice := uint64(0); slice <= maxSlices[indexName]; slice++ { + // Ignore this slice if we don't own it. + if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { + continue + } + + // Loop over view names. + for _, view := range views { + // Create view. + v, err := f.CreateViewIfNotExists(view) + if err != nil { + return err + } + + // Otherwise retrieve the local fragment. + frag, err := v.CreateFragmentIfNotExists(slice) + if err != nil { + return err + } + + // Stream backup from remote node. + rd, err := client.BackupSlice(ctx, indexName, frameName, view, slice) + if err != nil { + return err + } else if rd == nil { + continue // slice doesn't exist + } + + // Restore to local frame and always close reader. + if err := func() error { + defer rd.Close() + if _, err := frag.ReadFrom(rd); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + } + + return nil +} + +func (api *API) ClusterHosts(ctx context.Context) []*Node { + return api.Cluster.Nodes +} + +func (api *API) CreateInputDefinition(ctx context.Context, indexName string, inputDefName string, inputDef InputDefinitionInfo) error { + // Find index. + index := api.Holder.Index(indexName) + if index == nil { + return ErrIndexNotFound + } + + if err := inputDef.Validate(); err != nil { + return err + } + + // Encode InputDefinition to its internal representation. + def := inputDef.Encode() + def.Name = inputDefName + + // Create InputDefinition. + if _, err := index.CreateInputDefinition(def); err != nil { + return err + } + + err := api.Broadcaster.SendSync( + &internal.CreateInputDefinitionMessage{ + Index: indexName, + Definition: def, + }) + if err != nil { + api.logger.Printf("problem sending CreateInputDefinition message: %s", err) + } + return nil +} + +func (api *API) InputDefinition(ctx context.Context, indexName string, inputDefName string) (*InputDefinition, error) { + // Find index. + index := api.Holder.Index(indexName) + if index == nil { + return nil, ErrIndexNotFound + } + + inputDef, err := index.InputDefinition(inputDefName) + if err != nil { + return nil, err + } + return inputDef, nil +} + +func (api *API) DeleteInputDefinition(ctx context.Context, indexName string, inputDefName string) error { + // Find index. + index := api.Holder.Index(indexName) + if index == nil { + return ErrIndexNotFound + } + + // Delete input definition from the index. + if err := index.DeleteInputDefinition(inputDefName); err != nil { + return err + } + + err := api.Broadcaster.SendSync( + &internal.DeleteInputDefinitionMessage{ + Index: indexName, + Name: inputDefName, + }) + if err != nil { + api.logger.Printf("problem sending DeleteInputDefinition message: %s", err) + } + return nil +} + +func (api *API) WriteInput(ctx context.Context, indexName string, inputDefName string, reqs []interface{}) error { + // Find index. + index := api.Holder.Index(indexName) + if index == nil { + return ErrIndexNotFound + } + + for _, req := range reqs { + bits, err := api.inputJSONDataParser(req.(map[string]interface{}), index, inputDefName) + if err != nil { + return err + } + for fr, bs := range bits { + if err := index.InputBits(fr, bs); err != nil { + return err + } + } + } + + return nil +} + +func (api *API) RecalculateCaches(ctx context.Context) { + api.Holder.RecalculateCaches() +} + +func (api *API) PostClusterMessage(ctx context.Context, pb proto.Message) error { + // Forward the error message. + if err := api.BroadcastHandler.ReceiveMessage(pb); err != nil { + return err + } + return nil +} + +func (api *API) LocalID(ctx context.Context) string { + return api.Holder.LocalID +} + +func (api *API) Schema(ctx context.Context) []*IndexInfo { + return api.Holder.Schema() +} + +func (api *API) Status(ctx context.Context) (proto.Message, error) { + return api.StatusHandler.ClusterStatus() +} + +func (api *API) CreateFrameField(ctx context.Context, indexName string, frameName string, field *Field) error { + // Retrieve frame by name. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return ErrFrameNotFound + } + + // Create new field. + if err := f.CreateField(field); err != nil { + return err + } + + // Send the create field message to all nodes. + err := api.Broadcaster.SendSync( + &internal.CreateFieldMessage{ + Index: indexName, + Frame: frameName, + Field: encodeField(field), + }) + if err != nil { + api.logger.Printf("problem sending CreateField message: %s", err) + } + return err +} + +func (api *API) DeleteFrameField(ctx context.Context, indexName string, frameName string, fieldName string) error { + // Retrieve frame by name. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return ErrFrameNotFound + } + + // Delete field. + if err := f.DeleteField(fieldName); err != nil { + return err + } + + // Send the delete field message to all nodes. + err := api.Broadcaster.SendSync( + &internal.DeleteFieldMessage{ + Index: indexName, + Frame: frameName, + Field: fieldName, + }) + if err != nil { + api.logger.Printf("problem sending DeleteField message: %s", err) + } + return err +} + +func (api *API) FrameFields(ctx context.Context, indexName string, frameName string) (*FrameSchema, error) { + index := api.Holder.index(indexName) + if index == nil { + return nil, ErrIndexNotFound + } + + frame := index.frame(frameName) + if frame == nil { + return nil, ErrFrameNotFound + } + + return frame.GetFields() +} + +func (api *API) FrameViews(ctx context.Context, indexName string, frameName string) ([]*View, error) { + // Retrieve views. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return nil, ErrFrameNotFound + } + + // Fetch views. + views := f.Views() + return views, nil +} + +func (api *API) DeleteView(ctx context.Context, indexName string, frameName string, viewName string) error { + // Retrieve frame. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return ErrFrameNotFound + } + + // Delete the view. + if err := f.DeleteView(viewName); err != nil { + // Ingore this error becuase views do not exist on all nodes due to slice distribution. + if err != ErrInvalidView { + return err + } + } + + // Send the delete view message to all nodes. + err := api.Broadcaster.SendSync( + &internal.DeleteViewMessage{ + Index: indexName, + Frame: frameName, + View: viewName, + }) + if err != nil { + api.logger.Printf("problem sending DeleteView message: %s", err) + } + + return err +} + +// InputJSONDataParser validates input json file and executes SetBit. +func (api *API) inputJSONDataParser(req map[string]interface{}, index *Index, name string) (map[string][]*Bit, error) { + inputDef, err := index.InputDefinition(name) + if err != nil { + return nil, err + } + // If field in input data is not in defined definition, return error. + var colValue uint64 + validFields := make(map[string]bool) + timestampFrame := make(map[string]int64) + for _, field := range inputDef.Fields() { + validFields[field.Name] = true + if field.PrimaryKey { + value, ok := req[field.Name] + if !ok { + return nil, fmt.Errorf("primary key does not exist") + } + rawValue, ok := value.(float64) // The default JSON marshalling will interpret this as a float + if !ok { + return nil, fmt.Errorf("float64 require, got value:%s, type: %s", value, reflect.TypeOf(value)) + } + colValue = uint64(rawValue) + } + // Find frame that need to add timestamp. + for _, action := range field.Actions { + if action.ValueDestination == InputSetTimestamp { + timestampFrame[action.Frame], err = GetTimeStamp(req, field.Name) + if err != nil { + return nil, err + } + } + } + } + + for key := range req { + _, ok := validFields[key] + if !ok { + return nil, fmt.Errorf("field not found: %s", key) + } + } + + setBits := make(map[string][]*Bit) + + for _, field := range inputDef.Fields() { + // skip field that defined in definition but not in input data + if _, ok := req[field.Name]; !ok { + continue + } + + // Looking into timestampFrame map and set timestamp to the whole frame + for _, action := range field.Actions { + frame := action.Frame + timestamp := timestampFrame[action.Frame] + // Skip input data field values that are set to null + if req[field.Name] == nil { + continue + } + bit, err := HandleAction(action, req[field.Name], colValue, timestamp) + if err != nil { + return nil, fmt.Errorf("error handling action: %s, err: %s", action.ValueDestination, err) + } + if bit != nil { + setBits[frame] = append(setBits[frame], bit) + } + } + } + return setBits, nil +} From 72526f753ce0d05033df6e1dbade86d291212fca Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Mon, 5 Mar 2018 18:28:24 +0300 Subject: [PATCH 04/12] Moved more of handler to API --- api.go | 2 +- handler.go | 478 ++++++++++++++++------------------------------------- 2 files changed, 141 insertions(+), 339 deletions(-) diff --git a/api.go b/api.go index e544d2d3f..84c2f5146 100644 --- a/api.go +++ b/api.go @@ -484,7 +484,7 @@ func (api *API) PostClusterMessage(ctx context.Context, pb proto.Message) error } func (api *API) LocalID(ctx context.Context) string { - return api.Holder.LocalID + return api.Cluster.Node.ID } func (api *API) Schema(ctx context.Context) []*IndexInfo { diff --git a/handler.go b/handler.go index bd47524ed..f9fc73c52 100644 --- a/handler.go +++ b/handler.go @@ -16,7 +16,6 @@ package pilosa import ( "context" - "encoding/csv" "encoding/json" "errors" "expvar" @@ -44,21 +43,7 @@ import ( // Handler represents an HTTP handler. type Handler struct { - Holder *Holder - Broadcaster Broadcaster - BroadcastHandler BroadcastHandler - StatusHandler StatusHandler - - FileSystem FileSystem - - // Local hostname & cluster configuration. - Node *Node - Cluster *Cluster - RemoteClient *http.Client - - Router *mux.Router - NormalRouter *mux.Router - RestrictedRouter *mux.Router + Router *mux.Router // The execution engine for running queries. Executor interface { @@ -295,8 +280,9 @@ func (h *Handler) handleWebUI(w http.ResponseWriter, r *http.Request) { // handleGetSchema handles GET /schema requests. func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { + schema := h.API.Schema(r.Context()) if err := json.NewEncoder(w).Encode(getSchemaResponse{ - Indexes: h.Holder.Schema(), + Indexes: schema, }); err != nil { h.Logger.Printf("write schema response error: %s", err) } @@ -304,7 +290,7 @@ func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { // handleGetStatus handles GET /status requests. func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { - pb, err := h.StatusHandler.ClusterStatus() + status, err := h.API.Status(r.Context()) if err != nil { h.Logger.Printf("cluster status error: %s", err) return @@ -774,13 +760,6 @@ func (h *Handler) handlePostFrameField(w http.ResponseWriter, r *http.Request) { return } - // Retrieve frame by name. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - field := &Field{ Name: fieldName, Type: req.Type, @@ -788,23 +767,15 @@ func (h *Handler) handlePostFrameField(w http.ResponseWriter, r *http.Request) { Max: req.Max, } - // Create new field. - if err := f.CreateField(field); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if err := h.API.CreateFrameField(r.Context(), indexName, frameName, field); err != nil { + if err == ErrFrameNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } - // Send the create field message to all nodes. - err := h.Broadcaster.SendSync( - &internal.CreateFieldMessage{ - Index: indexName, - Frame: frameName, - Field: encodeField(field), - }) - if err != nil { - h.Logger.Printf("problem sending CreateField message: %s", err) - } - // Encode response. if err := json.NewEncoder(w).Encode(postFrameFieldResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) @@ -825,30 +796,15 @@ func (h *Handler) handleDeleteFrameField(w http.ResponseWriter, r *http.Request) frameName := mux.Vars(r)["frame"] fieldName := mux.Vars(r)["field"] - // Retrieve frame by name. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) + if err := h.API.DeleteFrameField(r.Context(), indexName, frameName, fieldName); err != nil { + if err == ErrFrameNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } - // Delete field. - if err := f.DeleteField(fieldName); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Send the delete field message to all nodes. - err := h.Broadcaster.SendSync( - &internal.DeleteFieldMessage{ - Index: indexName, - Frame: frameName, - Field: fieldName, - }) - if err != nil { - h.Logger.Printf("problem sending DeleteField message: %s", err) - } - // Encode response. if err := json.NewEncoder(w).Encode(deleteFrameFieldResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) @@ -859,24 +815,18 @@ func (h *Handler) handleGetFrameFields(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] frameName := mux.Vars(r)["frame"] - index := h.Holder.index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - frame := index.frame(frameName) - if frame == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - fields, err := frame.GetFields() - if err == ErrFrameFieldsNotAllowed { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } else if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + schema, err := h.API.FrameFields(r.Context(), indexName, frameName) + if err != nil { + switch err { + case ErrIndexNotFound: + fallthrough + case ErrFrameNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + case ErrFrameFieldsNotAllowed: + http.Error(w, err.Error(), http.StatusBadRequest) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } @@ -899,15 +849,16 @@ func (h *Handler) handleGetFrameViews(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] frameName := mux.Vars(r)["frame"] - // Retrieve views. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) + views, err := h.API.FrameViews(r.Context(), indexName, frameName) + if err != nil { + if err == ErrFrameNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } - // Fetch views. - views := f.Views() names := make([]string, len(views)) for i := range views { names[i] = views[i].Name() @@ -925,31 +876,13 @@ func (h *Handler) handleDeleteView(w http.ResponseWriter, r *http.Request) { frameName := mux.Vars(r)["frame"] viewName := mux.Vars(r)["view"] - // Retrieve frame. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Delete the view. - if err := f.DeleteView(viewName); err != nil { - // Ingore this error because views do not exist on all nodes due to slice distribution. - if err != ErrInvalidView { + if err := h.API.DeleteView(r.Context(), indexName, frameName, viewName); err != nil { + if err == ErrFrameNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { http.Error(w, err.Error(), http.StatusBadRequest) - return } - } - - // Send the delete view message to all nodes. - err := h.Broadcaster.SendSync( - &internal.DeleteViewMessage{ - Index: indexName, - Frame: frameName, - View: viewName, - }) - if err != nil { - h.Logger.Printf("problem sending DeleteView message: %s", err) + return } // Encode response. @@ -1314,28 +1247,13 @@ func (h *Handler) handleGetExportCSV(w http.ResponseWriter, r *http.Request) { return } - // Find the fragment. - f := h.Holder.Fragment(index, frame, view, slice) - if f == nil { - return + if err = h.API.ExportCSV(r.Context(), index, frame, view, slice, w); err != nil { + if err == ErrFragmentNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } } - - // Wrap writer with a CSV writer. - cw := csv.NewWriter(w) - - // Iterate over each bit. - if err := f.ForEachBit(func(rowID, columnID uint64) error { - return cw.Write([]string{ - strconv.FormatUint(rowID, 10), - strconv.FormatUint(columnID, 10), - }) - }); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Ensure data is flushed. - cw.Flush() } // handleGetFragmentNodes handles /fragment/nodes requests. @@ -1351,7 +1269,7 @@ func (h *Handler) handleGetFragmentNodes(w http.ResponseWriter, r *http.Request) } // Retrieve fragment owner nodes. - nodes := h.Cluster.FragmentNodes(index, slice) + nodes := h.API.FragmentNodes(r.Context(), index, slice) // Write to response. if err := json.NewEncoder(w).Encode(nodes); err != nil { @@ -1370,9 +1288,9 @@ func (h *Handler) handleGetFragmentData(w http.ResponseWriter, r *http.Request) } // Retrieve fragment from holder. - f := h.Holder.Fragment(q.Get("index"), q.Get("frame"), q.Get("view"), slice) - if f == nil { - http.Error(w, "fragment not found", http.StatusNotFound) + f, err := h.API.FragmentData(r.Context(), q.Get("index"), q.Get("frame"), q.Get("view"), slice) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) return } @@ -1392,31 +1310,12 @@ func (h *Handler) handlePostFragmentData(w http.ResponseWriter, r *http.Request) return } - // Retrieve frame. - f := h.Holder.Frame(q.Get("index"), q.Get("frame")) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Retrieve view. - view, err := f.CreateViewIfNotExists(q.Get("view")) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Retrieve fragment from frame. - frag, err := view.CreateFragmentIfNotExists(slice) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Read fragment in from request body. - if _, err := frag.ReadFrom(r.Body); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return + if err = h.API.WriteFragmentData(r.Context(), q.Get("index"), q.Get("frame"), q.Get("view"), slice, r.Body); err != nil { + if err == ErrFrameNotFound { + http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } } } @@ -1432,19 +1331,16 @@ func (h *Handler) handleGetFragmentBlockData(w http.ResponseWriter, r *http.Requ return } - // Retrieve fragment from holder. - f := h.Holder.Fragment(req.Index, req.Frame, req.View, req.Slice) - if f == nil { - http.Error(w, ErrFragmentNotFound.Error(), http.StatusNotFound) + resp, err := h.API.FragmentBlockData(r.Context(), req) + if err != nil { + if err == ErrFragmentNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } - // Read data - var resp internal.BlockDataResponse - if f != nil { - resp.RowIDs, resp.ColumnIDs = f.BlockData(int(req.Block)) - } - // Encode response. buf, err := proto.Marshal(&resp) if err != nil { @@ -1468,16 +1364,16 @@ func (h *Handler) handleGetFragmentBlocks(w http.ResponseWriter, r *http.Request return } - // Retrieve fragment from holder. - f := h.Holder.Fragment(q.Get("index"), q.Get("frame"), q.Get("view"), slice) - if f == nil { - http.Error(w, "fragment not found", http.StatusNotFound) + blocks, err := h.API.FragmentBlocks(r.Context(), q.Get("index"), q.Get("frame"), q.Get("view"), slice) + if err != nil { + if err == ErrFragmentNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } - // Retrieve blocks. - blocks := f.Blocks() - // Encode response. if err := json.NewEncoder(w).Encode(getFragmentBlocksResponse{ Blocks: blocks, @@ -1509,80 +1405,23 @@ func (h *Handler) handlePostFrameRestore(w http.ResponseWriter, r *http.Request) http.Error(w, err.Error(), http.StatusBadRequest) } - // Create a client for the remote cluster. - client := NewInternalHTTPClientFromURI(host, h.RemoteClient) - - // Determine the maximum number of slices. - maxSlices, err := client.MaxSliceByIndex(r.Context()) - if err != nil { - http.Error(w, "cannot determine remote slice count: "+err.Error(), http.StatusInternalServerError) - return - } - - // Retrieve frame. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Retrieve list of all views. - views, err := client.FrameViews(r.Context(), indexName, frameName) - if err != nil { - http.Error(w, "cannot retrieve frame views: "+err.Error(), http.StatusInternalServerError) - return - } - - // Loop over each slice and import it if this node owns it. - for slice := uint64(0); slice <= maxSlices[indexName]; slice++ { - // Ignore this slice if we don't own it. - if !h.Cluster.OwnsFragment(h.Node.ID, indexName, slice) { - continue - } - - // Loop over view names. - for _, view := range views { - // Create view. - v, err := f.CreateViewIfNotExists(view) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Otherwise retrieve the local fragment. - frag, err := v.CreateFragmentIfNotExists(slice) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Stream backup from remote node. - rd, err := client.BackupSlice(r.Context(), indexName, frameName, view, slice) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } else if rd == nil { - continue // slice doesn't exist - } - - // Restore to local frame and always close reader. - if err := func() error { - defer rd.Close() - if _, err := frag.ReadFrom(rd); err != nil { - return err - } - return nil - }(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } + err = h.API.RestoreFrame(r.Context(), indexName, frameName, host) + switch err { + case nil: + break + case ErrFrameNotFound: + fallthrough + case ErrFragmentNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) } } // handleGetHosts handles /hosts requests. func (h *Handler) handleGetHosts(w http.ResponseWriter, r *http.Request) { - if err := json.NewEncoder(w).Encode(h.Cluster.Nodes); err != nil { + hosts := h.API.ClusterHosts(r.Context()) + if err := json.NewEncoder(w).Encode(hosts); err != nil { h.Logger.Printf("write version response error: %s", err) } } @@ -1766,13 +1605,6 @@ func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Reque indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] - // Find index. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - // Decode request. var req InputDefinitionInfo err := json.NewDecoder(r.Body).Decode(&req) @@ -1781,36 +1613,26 @@ func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Reque return } - if err := req.Validate(); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - // Encode InputDefinition to its internal representation. - def := req.Encode() - def.Name = inputDefName - - // Create InputDefinition. - _, err = index.CreateInputDefinition(def) - if err == ErrInputDefinitionExists { + err = h.API.CreateInputDefinition(r.Context(), indexName, inputDefName, req) + switch err { + case nil: + break + case ErrIndexNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + case ErrInputDefinitionExists: http.Error(w, err.Error(), http.StatusConflict) - return - } else if err != nil { + case ErrInputDefinitionAttrsRequired: + fallthrough + case ErrInputDefinitionNameRequired: + fallthrough + case ErrInputDefinitionActionRequired: + fallthrough + case ErrInputDefinitionHasPrimaryKey: + fallthrough + case ErrInputDefinitionDupePrimaryKey: + http.Error(w, err.Error(), http.StatusBadRequest) + default: http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - err = h.Broadcaster.SendSync( - &internal.CreateInputDefinitionMessage{ - Index: indexName, - Definition: def, - }) - if err != nil { - h.Logger.Printf("problem sending CreateInputDefinition message: %s", err) - } - - if err := json.NewEncoder(w).Encode(defaultInputDefinitionResponse{}); err != nil { - h.Logger.Printf("response encoding error: %s", err) } } @@ -1819,16 +1641,18 @@ func (h *Handler) handleGetInputDefinition(w http.ResponseWriter, r *http.Reques indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] - // Find index. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - inputDef, err := index.InputDefinition(inputDefName) + inputDef, err := h.API.InputDefinition(r.Context(), indexName, inputDefName) if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) + switch err { + case nil: + break + case ErrIndexNotFound: + fallthrough + case ErrInputDefinitionNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } @@ -1838,7 +1662,6 @@ func (h *Handler) handleGetInputDefinition(w http.ResponseWriter, r *http.Reques }); err != nil { h.Logger.Printf("write status response error: %s", err) } - } // handleDeleteInputDefinition handles DELETE /input-definition request. @@ -1846,28 +1669,20 @@ func (h *Handler) handleDeleteInputDefinition(w http.ResponseWriter, r *http.Req indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] - // Find index. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) + if err := h.API.DeleteInputDefinition(r.Context(), indexName, inputDefName); err != nil { + switch err { + case nil: + break + case ErrIndexNotFound: + fallthrough + case ErrInputDefinitionNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + default: + http.Error(w, err.Error(), http.StatusNotFound) + } return } - // Delete input definition from the index. - if err := index.DeleteInputDefinition(inputDefName); err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - err := h.Broadcaster.SendSync( - &internal.DeleteInputDefinitionMessage{ - Index: indexName, - Name: inputDefName, - }) - if err != nil { - h.Logger.Printf("problem sending DeleteInputDefinition message: %s", err) - } - if err := json.NewEncoder(w).Encode(defaultInputDefinitionResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) } @@ -1879,13 +1694,6 @@ func (h *Handler) handlePostInput(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] inputDefName := mux.Vars(r)["input-definition"] - // Find index. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - // Decode request. var reqs []interface{} err := json.NewDecoder(r.Body).Decode(&reqs) @@ -1893,28 +1701,27 @@ func (h *Handler) handlePostInput(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusBadRequest) return } - for _, req := range reqs { - bits, err := h.InputJSONDataParser(req.(map[string]interface{}), index, inputDefName) - if err == ErrInputDefinitionNotFound { + + if err = h.API.WriteInput(r.Context(), indexName, inputDefName, reqs); err != nil { + switch err { + case nil: + break + case ErrIndexNotFound: + fallthrough + case ErrInputDefinitionNotFound: http.Error(w, err.Error(), http.StatusNotFound) - return - } else if err != nil { + default: http.Error(w, err.Error(), http.StatusBadRequest) - return - } - for fr, bs := range bits { - err := index.InputBits(fr, bs) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } } + return } + if err := json.NewEncoder(w).Encode(defaultInputDefinitionResponse{}); err != nil { h.Logger.Printf("response encoding error: %s", err) } } +// <<<<<<< b702f70610962341116d9678ce7c5877b7e171ca // handlePostClusterResizeSetCoordinator handles POST /cluster/resize/set-coordinator request. func (h *Handler) handlePostClusterResizeSetCoordinator(w http.ResponseWriter, r *http.Request) { // Decode request. @@ -2109,14 +1916,11 @@ func (h *Handler) InputJSONDataParser(req map[string]interface{}, index *Index, return setBits, nil } +// ======= +// >>>>>>> Moved more of handler to API + func (h *Handler) handleRecalculateCaches(w http.ResponseWriter, r *http.Request) { - err := h.Broadcaster.SendSync(&internal.RecalculateCaches{}) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - h.writeQueryResponse(w, r, &QueryResponse{Err: err}) - return - } - h.Holder.RecalculateCaches() + h.API.RecalculateCaches(r.Context()) w.WriteHeader(http.StatusNoContent) } @@ -2161,9 +1965,7 @@ func (h *Handler) handlePostClusterMessage(w http.ResponseWriter, r *http.Reques return } - // Forward the error message. - err = h.BroadcastHandler.ReceiveMessage(pb) - if err != nil { + if err := h.API.PostClusterMessage(r.Context(), pb); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -2174,7 +1976,7 @@ func (h *Handler) handlePostClusterMessage(w http.ResponseWriter, r *http.Reques } func (h *Handler) handleGetID(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte(h.Cluster.Node.ID)) + _, err := w.Write([]byte(h.API.LocalID(r.Context()))) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } From 8336784d57d48827cf60787ce5d431f1c2a56d93 Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Tue, 6 Mar 2018 17:28:33 +0300 Subject: [PATCH 05/12] More API updates; removed Cluster, Holder, etc from Handler --- api.go | 168 ++++++++++++++++++++++++++++++- handler.go | 255 ++++++++++++++++------------------------------- pilosa.go | 4 + server.go | 12 +-- server/server.go | 3 +- 5 files changed, 262 insertions(+), 180 deletions(-) diff --git a/api.go b/api.go index 84c2f5146..f369ba10a 100644 --- a/api.go +++ b/api.go @@ -25,6 +25,7 @@ import ( "reflect" "strconv" "strings" + "time" "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa/internal" @@ -221,6 +222,12 @@ func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName str } func (api *API) ExportCSV(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, w io.Writer) error { + // Validate that this handler owns the slice. + if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { + api.logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) + return ErrClusterDoesNotOwnSlice + } + // Find the fragment. f := api.Holder.Fragment(indexName, frameName, viewName, slice) if f == nil { @@ -600,7 +607,166 @@ func (api *API) DeleteView(ctx context.Context, indexName string, frameName stri return err } -// InputJSONDataParser validates input json file and executes SetBit. +func (api *API) IndexAttrDiff(ctx context.Context, indexName string, blocks []AttrBlock) (map[uint64]map[string]interface{}, error) { + // Retrieve index from holder. + index := api.Holder.Index(indexName) + if index == nil { + return nil, ErrIndexNotFound + } + + // Retrieve local blocks. + localBlocks, err := index.ColumnAttrStore().Blocks() + if err != nil { + return nil, err + } + + // Read all attributes from all mismatched blocks. + attrs := make(map[uint64]map[string]interface{}) + for _, blockID := range AttrBlocks(localBlocks).Diff(blocks) { + // Retrieve block data. + m, err := index.ColumnAttrStore().BlockData(blockID) + if err != nil { + return nil, err + } + + // Copy to index-wide struct. + for k, v := range m { + attrs[k] = v + } + } + return attrs, nil +} + +func (api *API) FrameAttrDiff(ctx context.Context, indexName string, frameName string, blocks []AttrBlock) (map[uint64]map[string]interface{}, error) { + // Retrieve index from holder. + f := api.Holder.Frame(indexName, frameName) + if f == nil { + return nil, ErrFrameNotFound + } + + // Retrieve local blocks. + localBlocks, err := f.RowAttrStore().Blocks() + if err != nil { + return nil, err + } + + // Read all attributes from all mismatched blocks. + attrs := make(map[uint64]map[string]interface{}) + for _, blockID := range AttrBlocks(localBlocks).Diff(blocks) { + // Retrieve block data. + m, err := f.RowAttrStore().BlockData(blockID) + if err != nil { + return nil, err + } + + // Copy to index-wide struct. + for k, v := range m { + attrs[k] = v + } + } + return attrs, nil +} + +func (api *API) Import(ctx context.Context, req internal.ImportRequest) error { + _, frame, err := api.indexFrame(req.Index, req.Frame, req.Slice) + if err != nil { + return err + } + + // Convert timestamps to time.Time. + timestamps := make([]*time.Time, len(req.Timestamps)) + for i, ts := range req.Timestamps { + if ts == 0 { + continue + } + t := time.Unix(0, ts) + timestamps[i] = &t + } + + // Import into fragment. + err = frame.Import(req.RowIDs, req.ColumnIDs, timestamps) + if err != nil { + api.logger.Printf("import error: index=%s, frame=%s, slice=%d, bits=%d, err=%s", req.Index, req.Frame, req.Slice, len(req.ColumnIDs), err) + } + return err +} + +func (api *API) ImportValue(ctx context.Context, req internal.ImportValueRequest) error { + _, frame, err := api.indexFrame(req.Index, req.Frame, req.Slice) + if err != nil { + return err + } + + // Import into fragment. + err = frame.ImportValue(req.Field, req.ColumnIDs, req.Values) + if err != nil { + api.logger.Printf("import error: index=%s, frame=%s, slice=%d, field=%s, bits=%d, err=%s", req.Index, req.Frame, req.Slice, req.Field, len(req.ColumnIDs), err) + } + return err +} + +func (api *API) ModifyIndexTimeQuantum(ctx context.Context, indexName string, timeQuantum TimeQuantum) error { + // Retrieve index by name. + index := api.Holder.Index(indexName) + if index == nil { + return ErrIndexNotFound + } + + // Set default time quantum on index. + return index.SetTimeQuantum(timeQuantum) +} + +func (api *API) ModifyFrameTimeQuantum(ctx context.Context, indexName string, frameName string, timeQuantum TimeQuantum) error { + // Retrieve index by name. + frame := api.Holder.Frame(indexName, frameName) + if frame == nil { + return ErrFrameNotFound + } + + // Set default time quantum on index. + return frame.SetTimeQuantum(timeQuantum) +} + +func (api *API) SliceMax(ctx context.Context, inverse bool) map[string]uint64 { + if inverse { + return api.Holder.MaxInverseSlices() + } + return api.Holder.MaxSlices() +} + +func (api *API) StatsWithTags(tags []string) StatsClient { + return api.Holder.Stats.WithTags(tags...) +} + +func (api *API) ClusterLongQueryTime() time.Duration { + return api.Cluster.LongQueryTime +} + +func (api *API) indexFrame(indexName string, frameName string, slice uint64) (*Index, *Frame, error) { + // Validate that this handler owns the slice. + if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { + api.logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) + return nil, nil, ErrClusterDoesNotOwnSlice + } + + // Find the Index. + api.logger.Println("importing:", indexName, frameName, slice) + index := api.Holder.Index(indexName) + if index == nil { + api.logger.Printf("fragment error: index=%s, frame=%s, slice=%d, err=%s", indexName, frameName, slice, ErrIndexNotFound.Error()) + return nil, nil, ErrIndexNotFound + } + + // Retrieve frame. + frame := index.Frame(frameName) + if frame == nil { + api.logger.Printf("frame error: index=%s, frame=%s, slice=%d, err=%s", indexName, frameName, slice, ErrFrameNotFound.Error()) + return nil, nil, ErrFrameNotFound + } + return index, frame, nil +} + +// inputJSONDataParser validates input json file and executes SetBit. func (api *API) inputJSONDataParser(req map[string]interface{}, index *Index, name string) (map[string][]*Bit, error) { inputDef, err := index.InputDefinition(name) if err != nil { diff --git a/handler.go b/handler.go index f9fc73c52..0f04028e0 100644 --- a/handler.go +++ b/handler.go @@ -240,27 +240,26 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { dif := time.Since(t) // Calculate per request StatsD metrics when the handler is fully configured. - if h.Holder != nil && h.Cluster != nil { - statsTags := make([]string, 0, 3) + statsTags := make([]string, 0, 3) - if h.Cluster.LongQueryTime > 0 && dif > h.Cluster.LongQueryTime { - h.Logger.Printf("%s %s %v", r.Method, r.URL.String(), dif) - statsTags = append(statsTags, "slow_query") - } - - pathParts := strings.Split(r.URL.Path, "/") - endpointName := strings.Join(pathParts, "_") - - if externalPrefixFlag[pathParts[1]] { - statsTags = append(statsTags, "external") - } - - // useragent tag identifies internal/external endpoints - statsTags = append(statsTags, "useragent:"+r.UserAgent()) - - stats := h.Holder.Stats.WithTags(statsTags...) - stats.Histogram("http."+endpointName, float64(dif), 0.1) + longQueryTime := h.API.ClusterLongQueryTime() + if longQueryTime > 0 && dif > longQueryTime { + h.Logger.Printf("%s %s %v", r.Method, r.URL.String(), dif) + statsTags = append(statsTags, "slow_query") } + + pathParts := strings.Split(r.URL.Path, "/") + endpointName := strings.Join(pathParts, "_") + + if externalPrefixFlag[pathParts[1]] { + statsTags = append(statsTags, "external") + } + + // useragent tag identifies internal/external endpoints + statsTags = append(statsTags, "useragent:"+r.UserAgent()) + + stats := h.API.StatsWithTags(statsTags) + stats.Histogram("http."+endpointName, float64(dif), 0.1) } func (h *Handler) handleWebUI(w http.ResponseWriter, r *http.Request) { @@ -348,13 +347,23 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { } } -// handleGetSlicesMax handles GET /schema requests. -func (h *Handler) handleGetSlicesMax(w http.ResponseWriter, r *http.Request) { - if err := json.NewEncoder(w).Encode(getSlicesMaxResponse{ - Standard: h.Holder.MaxSlices(), - Inverse: h.Holder.MaxInverseSlices(), - }); err != nil { - h.Logger.Printf("write slices-max response error: %s", err) +func (h *Handler) handleGetSliceMax(w http.ResponseWriter, r *http.Request) { + inverse, err := strconv.ParseBool(r.URL.Query().Get("inverse")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + ms := h.API.SliceMax(r.Context(), inverse) + if strings.Contains(r.Header.Get("Accept"), "application/x-protobuf") { + pb := &internal.MaxSlicesResponse{ + MaxSlices: ms, + } + if buf, err := proto.Marshal(pb); err != nil { + h.Logger.Printf("protobuf marshal error: %s", err) + } else if _, err := w.Write(buf); err != nil { + h.Logger.Printf("stream write error: %s", err) + } + return } } @@ -518,16 +527,12 @@ func (h *Handler) handlePatchIndexTimeQuantum(w http.ResponseWriter, r *http.Req return } - // Retrieve index by name. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - // Set default time quantum on index. - if err := index.SetTimeQuantum(tq); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if err = h.API.ModifyIndexTimeQuantum(r.Context(), indexName, tq); err != nil { + if err == ErrIndexNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } @@ -554,34 +559,14 @@ func (h *Handler) handlePostIndexAttrDiff(w http.ResponseWriter, r *http.Request return } - // Retrieve index from holder. - index := h.Holder.Index(indexName) - if index == nil { - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - // Retrieve local blocks. - blks, err := index.ColumnAttrStore().Blocks() + attrs, err := h.API.IndexAttrDiff(r.Context(), indexName, req.Blocks) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Read all attributes from all mismatched blocks. - attrs := make(map[uint64]map[string]interface{}) - for _, blockID := range AttrBlocks(blks).Diff(req.Blocks) { - // Retrieve block data. - m, err := index.ColumnAttrStore().BlockData(blockID) - if err != nil { + if err == ErrIndexNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Copy to index-wide struct. - for k, v := range m { - attrs[k] = v } + return } // Encode response. @@ -722,16 +707,12 @@ func (h *Handler) handlePatchFrameTimeQuantum(w http.ResponseWriter, r *http.Req return } - // Retrieve index by name. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Set default time quantum on index. - if err := f.SetTimeQuantum(tq); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if err := h.API.ModifyFrameTimeQuantum(r.Context(), indexName, frameName, tq); err != nil { + if err == ErrFragmentNotFound { + http.Error(w, err.Error(), http.StatusNotFound) + } else { + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } @@ -909,34 +890,15 @@ func (h *Handler) handlePostFrameAttrDiff(w http.ResponseWriter, r *http.Request return } - // Retrieve index from holder. - f := h.Holder.Frame(indexName, frameName) - if f == nil { - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Retrieve local blocks. - blks, err := f.RowAttrStore().Blocks() + attrs, err := h.API.FrameAttrDiff(r.Context(), indexName, frameName, req.Blocks) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Read all attributes from all mismatched blocks. - attrs := make(map[uint64]map[string]interface{}) - for _, blockID := range AttrBlocks(blks).Diff(req.Blocks) { - // Retrieve block data. - m, err := f.RowAttrStore().BlockData(blockID) - if err != nil { + switch err { + case ErrFragmentNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + default: http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - // Copy to index-wide struct. - for k, v := range m { - attrs[k] = v } + return } // Encode response. @@ -1094,44 +1056,17 @@ func (h *Handler) handlePostImport(w http.ResponseWriter, r *http.Request) { return } - // Convert timestamps to time.Time. - timestamps := make([]*time.Time, len(req.Timestamps)) - for i, ts := range req.Timestamps { - if ts == 0 { - continue + if err := h.API.Import(r.Context(), req); err != nil { + switch err { + case ErrIndexNotFound: + fallthrough + case ErrFrameNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + case ErrClusterDoesNotOwnSlice: + http.Error(w, err.Error(), http.StatusPreconditionFailed) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) } - t := time.Unix(0, ts) - timestamps[i] = &t - } - - // Validate that this handler owns the slice. - if !h.Cluster.OwnsFragment(h.Node.ID, req.Index, req.Slice) { - msg := fmt.Sprintf("host does not own slice %s-%s slice:%d", h.Node.ID, req.Index, req.Slice) - http.Error(w, msg, http.StatusPreconditionFailed) - return - } - - // Find the Index. - h.Logger.Printf("importing: %s %s %d", req.Index, req.Frame, req.Slice) - index := h.Holder.Index(req.Index) - if index == nil { - h.Logger.Printf("fragment error: index=%s, frame=%s, slice=%d, err=%s", req.Index, req.Frame, req.Slice, ErrIndexNotFound.Error()) - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - // Retrieve frame. - f := index.Frame(req.Frame) - if f == nil { - h.Logger.Printf("frame error: index=%s, frame=%s, slice=%d, err=%s", req.Index, req.Frame, req.Slice, ErrFrameNotFound.Error()) - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Import into fragment. - err = f.Import(req.RowIDs, req.ColumnIDs, timestamps) - if err != nil { - h.Logger.Printf("import error: index=%s, frame=%s, slice=%d, bits=%d, err=%s", req.Index, req.Frame, req.Slice, len(req.ColumnIDs), err) return } @@ -1174,34 +1109,17 @@ func (h *Handler) handlePostImportValue(w http.ResponseWriter, r *http.Request) return } - // Validate that this handler owns the slice. - if !h.Cluster.OwnsFragment(h.Node.ID, req.Index, req.Slice) { - msg := fmt.Sprintf("host does not own slice %s-%s slice:%d", h.Node.ID, req.Index, req.Slice) - http.Error(w, msg, http.StatusPreconditionFailed) - return - } - - // Find the Index. - h.Logger.Printf("importing: %s %s %d", req.Index, req.Frame, req.Slice) - index := h.Holder.Index(req.Index) - if index == nil { - h.Logger.Printf("fragment error: index=%s, frame=%s, slice=%d, err=%s", req.Index, req.Frame, req.Slice, ErrIndexNotFound.Error()) - http.Error(w, ErrIndexNotFound.Error(), http.StatusNotFound) - return - } - - // Retrieve frame. - f := index.Frame(req.Frame) - if f == nil { - h.Logger.Printf("frame error: index=%s, frame=%s, slice=%d, err=%s", req.Index, req.Frame, req.Slice, ErrFrameNotFound.Error()) - http.Error(w, ErrFrameNotFound.Error(), http.StatusNotFound) - return - } - - // Import into fragment. - err = f.ImportValue(req.Field, req.ColumnIDs, req.Values) - if err != nil { - h.Logger.Printf("import error: index=%s, frame=%s, slice=%d, field=%s, bits=%d, err=%s", req.Index, req.Frame, req.Slice, req.Field, len(req.ColumnIDs), err) + if err = h.API.ImportValue(r.Context(), req); err != nil { + switch err { + case ErrIndexNotFound: + fallthrough + case ErrFrameNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + case ErrClusterDoesNotOwnSlice: + http.Error(w, err.Error(), http.StatusPreconditionFailed) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + } return } @@ -1240,19 +1158,16 @@ func (h *Handler) handleGetExportCSV(w http.ResponseWriter, r *http.Request) { return } - // Validate that this handler owns the slice. - if !h.Cluster.OwnsFragment(h.Node.ID, index, slice) { - msg := fmt.Sprintf("host does not own slice %s-%s slice:%d", h.Node.ID, index, slice) - http.Error(w, msg, http.StatusPreconditionFailed) - return - } - if err = h.API.ExportCSV(r.Context(), index, frame, view, slice, w); err != nil { - if err == ErrFragmentNotFound { + switch err { + case ErrFragmentNotFound: http.Error(w, err.Error(), http.StatusNotFound) - } else { + case ErrClusterDoesNotOwnSlice: + http.Error(w, err.Error(), http.StatusPreconditionFailed) + default: http.Error(w, err.Error(), http.StatusInternalServerError) } + return } } diff --git a/pilosa.go b/pilosa.go index 98c67203f..04c76a133 100644 --- a/pilosa.go +++ b/pilosa.go @@ -74,6 +74,10 @@ var ( ErrTooManyWrites = errors.New("too many write commands") ErrConfigClusterEnabledHosts = errors.New("providing hosts to a non-disabled cluster is not allowed") + ErrConfigClusterTypeInvalid = errors.New("invalid cluster type") + ErrConfigHostsMissing = errors.New("missing bind address in cluster hosts") + + ErrClusterDoesNotOwnSlice = errors.New("cluster does not own slice") ) // Regular expression to validate index and frame names. diff --git a/server.go b/server.go index 1973f746e..338183cf7 100644 --- a/server.go +++ b/server.go @@ -115,9 +115,8 @@ func NewServer() *Server { Logger: NopLogger, } - s.Handler.Holder = s.Holder - s.diagnostics.server = s s.Handler.API = NewAPI(s.logger) + s.Handler.API.Holder = s.Holder return s } @@ -165,12 +164,11 @@ func (s *Server) Open() error { s.Cluster.MaxWritesPerRequest = s.MaxWritesPerRequest // Initialize HTTP handler. - s.Handler.Broadcaster = s.Broadcaster s.Handler.API.Broadcaster = s.Broadcaster - s.Handler.BroadcastHandler = s - s.Handler.StatusHandler = s - s.Handler.Node = node - s.Handler.Cluster = s.Cluster + s.Handler.API.BroadcastHandler = s + s.Handler.API.StatusHandler = s + s.Handler.API.URI = s.URI + s.Handler.API.Cluster = s.Cluster s.Handler.Executor = e s.Cluster.prefect = s.Handler diff --git a/server/server.go b/server/server.go index 66725f4ee..a50ad3e71 100644 --- a/server/server.go +++ b/server/server.go @@ -218,8 +218,7 @@ func (m *Command) SetupServer() error { } c := pilosa.GetHTTPClient(TLSConfig) m.Server.RemoteClient = c - m.Server.Handler.RemoteClient = c - m.Server.Cluster.RemoteClient = c + m.Server.Handler.API.RemoteClient = c // Statik file system. m.Server.Handler.FileSystem = &statik.FileSystem{} From c36dd395e664be375d37fe70f9988d5104a20d57 Mon Sep 17 00:00:00 2001 From: Yuce Tekol Date: Wed, 7 Mar 2018 15:39:44 +0300 Subject: [PATCH 06/12] All tests pass --- api.go | 7 +- client_test.go | 67 ++++++++++--------- ctl/backup_test.go | 10 ++- ctl/export_test.go | 10 ++- ctl/import_test.go | 21 +++--- ctl/restore_test.go | 10 ++- executor_test.go | 10 +-- handler.go | 67 +++++++++++-------- handler_test.go | 158 ++++++++++++++++++++++---------------------- holder_test.go | 2 +- stats_test.go | 16 ++--- test/handler.go | 33 ++++++--- 12 files changed, 218 insertions(+), 193 deletions(-) diff --git a/api.go b/api.go index f369ba10a..97d7828e5 100644 --- a/api.go +++ b/api.go @@ -61,7 +61,6 @@ func (a *API) ExecuteQuery(ctx context.Context, req *QueryRequest) (QueryRespons q, err := pql.NewParser(strings.NewReader(req.Query)).Parse() if err != nil { - // TODO: Wrap return resp, err } execOpts := &ExecOptions{ @@ -735,10 +734,16 @@ func (api *API) SliceMax(ctx context.Context, inverse bool) map[string]uint64 { } func (api *API) StatsWithTags(tags []string) StatsClient { + if api.Holder == nil || api.Cluster == nil { + return nil + } return api.Holder.Stats.WithTags(tags...) } func (api *API) ClusterLongQueryTime() time.Duration { + if api.Cluster == nil { + return 0 + } return api.Cluster.LongQueryTime } diff --git a/client_test.go b/client_test.go index f2bd18e50..36b9dc38d 100644 --- a/client_test.go +++ b/client_test.go @@ -36,10 +36,10 @@ func createCluster(c *pilosa.Cluster) ([]*test.Server, []*test.Holder) { for i := 0; i < numNodes; i++ { hldr[i] = test.MustOpenHolder() server[i] = test.NewServer() - server[i].Handler.Cluster = c - server[i].Handler.Cluster.Nodes[i].URI = server[i].HostURI() - server[i].Handler.Holder = hldr[i].Holder - server[i].Handler.Node = server[i].Handler.Cluster.Nodes[i] + server[i].Handler.API.URI = server[i].HostURI() + server[i].Handler.API.Cluster = c + server[i].Handler.API.Cluster.Nodes[i].URI = server[i].HostURI() + server[i].Handler.API.Holder = hldr[i].Holder } return server, hldr } @@ -86,7 +86,7 @@ func TestClient_MultiNode(t *testing.T) { // Create a dispersed set of bitmaps across 3 nodes such that each individual node and slice width increment would reveal a different TopN. sliceNums := []uint64{1, 2, 6} for i, num := range sliceNums { - owns := s[i].Handler.Handler.Cluster.OwnsSlices("i", 20, s[i].HostURI()) + owns := s[i].Handler.Handler.API.Cluster.OwnsSlices("i", 20, s[i].Host()) ownsNum := false for _, ownNum := range owns { if ownNum == num { @@ -217,10 +217,10 @@ func TestClient_Import(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder // Send import request. c := test.MustNewClient(s.Host(), defaultClient) @@ -268,10 +268,10 @@ func TestClient_ImportInverseEnabled(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder // Send import request. c := test.MustNewClient(s.Host(), defaultClient) @@ -317,10 +317,10 @@ func TestClient_ImportValue(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder // Send import request. c := test.MustNewClient(s.Host(), defaultClient) @@ -355,10 +355,10 @@ func TestClient_BackupRestore(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder c := test.MustNewClient(s.Host(), defaultClient) @@ -420,10 +420,11 @@ func TestClient_BackupInverseView(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder c := test.MustNewClient(s.Host(), defaultClient) @@ -457,10 +458,10 @@ func TestClient_BackupInvalidView(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder c := test.MustNewClient(s.Host(), defaultClient) @@ -486,10 +487,10 @@ func TestClient_FragmentBlocks(t *testing.T) { s := test.NewServer() defer s.Close() - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.Holder = hldr.Holder - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.URI = s.HostURI() + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder // Retrieve blocks. c := test.MustNewClient(s.Host(), defaultClient) diff --git a/ctl/backup_test.go b/ctl/backup_test.go index d23b1a917..7a4feb3d9 100644 --- a/ctl/backup_test.go +++ b/ctl/backup_test.go @@ -50,12 +50,10 @@ func TestBackupCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - node := &pilosa.Node{ID: "node", URI: *uri} - - s.Handler.Node = node - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = *uri - s.Handler.Holder = hldr.Holder + s.Handler.API.URI = uri + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder cm := NewBackupCommand(stdin, stdout, stderr) file, err := ioutil.TempFile("", "import.csv") diff --git a/ctl/export_test.go b/ctl/export_test.go index 5d1d5a4d7..2bcccd6ba 100644 --- a/ctl/export_test.go +++ b/ctl/export_test.go @@ -63,12 +63,10 @@ func TestExportCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - node := &pilosa.Node{ID: "node", URI: *uri} - - s.Handler.Node = node - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0] = node - s.Handler.Holder = hldr.Holder + s.Handler.API.URI = uri + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder cm.Host = s.Host() http.DefaultClient.Do(test.MustNewHTTPRequest("POST", s.URL+"/index/i", strings.NewReader(""))) diff --git a/ctl/import_test.go b/ctl/import_test.go index 9022efae9..119a56071 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -69,12 +69,10 @@ func TestImportCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - node := &pilosa.Node{ID: "node", URI: *uri} - - s.Handler.Node = node - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0] = node - s.Handler.Holder = hldr.Holder + s.Handler.API.URI = uri + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder cm.Host = s.Host() cm.Index = "i" @@ -111,16 +109,15 @@ func TestImportCommand_RunValue(t *testing.T) { if err != nil { t.Fatal(err) } - node := &pilosa.Node{ID: "node", URI: *uri} - s.Handler.Node = node - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0] = node - s.Handler.Holder = hldr.Holder + s.Handler.API.URI = uri + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder cm.Host = s.Host() http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i", strings.NewReader(""))) - http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i/frame/f", strings.NewReader(""))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i/frame/f", strings.NewReader(`{"options":{"rangeEnabled": true, "fields": [{"name": "foo", "type": "int", "min": 0, "max": 100}]}}`))) cm.Index = "i" cm.Frame = "f" diff --git a/ctl/restore_test.go b/ctl/restore_test.go index bb8eb3b5f..f1edb7fe9 100644 --- a/ctl/restore_test.go +++ b/ctl/restore_test.go @@ -52,12 +52,10 @@ func TestRestoreCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - node := &pilosa.Node{ID: "node", URI: *uri} - - s.Handler.Node = node - s.Handler.Cluster = test.NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = *uri - s.Handler.Holder = hldr.Holder + s.Handler.API.URI = uri + s.Handler.API.Cluster = test.NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + s.Handler.API.Holder = hldr.Holder cm := NewRestoreCommand(stdin, stdout, stderr) cm.Path = file.Name() diff --git a/executor_test.go b/executor_test.go index c98239137..690bfc5ce 100644 --- a/executor_test.go +++ b/executor_test.go @@ -927,7 +927,7 @@ func TestExecutor_Execute_Remote_Bitmap(t *testing.T) { // The local node owns slice 1. hldr := test.MustOpenHolder() defer hldr.Close() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder hldr.MustCreateFragmentIfNotExists("i", "f", pilosa.ViewStandard, 1).MustSetBits(10, (1*SliceWidth)+1) e := test.NewExecutor(hldr.Holder, c) @@ -961,7 +961,7 @@ func TestExecutor_Execute_Remote_Count(t *testing.T) { // Create local executor data. The local node owns slice 1. hldr := test.MustOpenHolder() defer hldr.Close() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder hldr.MustCreateFragmentIfNotExists("i", "f", pilosa.ViewStandard, 2).MustSetBits(10, (2*SliceWidth)+1) hldr.MustCreateFragmentIfNotExists("i", "f", pilosa.ViewStandard, 2).MustSetBits(10, (2*SliceWidth)+2) @@ -1004,7 +1004,7 @@ func TestExecutor_Execute_Remote_SetBit(t *testing.T) { // Create local executor data. hldr := test.MustOpenHolder() defer hldr.Close() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder // Create frame. if _, err := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}).CreateFrame("f", pilosa.FrameOptions{}); err != nil { @@ -1056,7 +1056,7 @@ func TestExecutor_Execute_Remote_SetBit_With_Timestamp(t *testing.T) { // Create local executor data. hldr := test.MustOpenHolder() defer hldr.Close() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder // Create frame. if f, err := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}).CreateFrame("f", pilosa.FrameOptions{}); err != nil { @@ -1130,7 +1130,7 @@ func TestExecutor_Execute_Remote_TopN(t *testing.T) { // Create local executor data on slice 2 & 4. hldr := test.MustOpenHolder() defer hldr.Close() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 2).MustSetBits(30, (2*SliceWidth)+1) hldr.MustCreateRankedFragmentIfNotExists("i", "f", pilosa.ViewStandard, 4).MustSetBits(30, (4*SliceWidth)+2) diff --git a/handler.go b/handler.go index 0f04028e0..f6a385ccb 100644 --- a/handler.go +++ b/handler.go @@ -257,9 +257,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // useragent tag identifies internal/external endpoints statsTags = append(statsTags, "useragent:"+r.UserAgent()) - stats := h.API.StatsWithTags(statsTags) - stats.Histogram("http."+endpointName, float64(dif), 0.1) + if stats != nil { + stats.Histogram("http."+endpointName, float64(dif), 0.1) + } } func (h *Handler) handleWebUI(w http.ResponseWriter, r *http.Request) { @@ -328,7 +329,8 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { resp, err := h.API.ExecuteQuery(r.Context(), req) if err != nil { w.WriteHeader(http.StatusBadRequest) - h.writeQueryResponse(w, r, &resp) + h.writeQueryResponse(w, r, &QueryResponse{Err: err}) + return } // Set appropriate status code, if there is an error. @@ -348,10 +350,15 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { } func (h *Handler) handleGetSliceMax(w http.ResponseWriter, r *http.Request) { - inverse, err := strconv.ParseBool(r.URL.Query().Get("inverse")) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return + var err error + inverse := false + inverseStr := r.URL.Query().Get("inverse") + if inverseStr != "" { + inverse, err = strconv.ParseBool(r.URL.Query().Get("inverse")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } } ms := h.API.SliceMax(r.Context(), inverse) if strings.Contains(r.Header.Get("Accept"), "application/x-protobuf") { @@ -1161,7 +1168,7 @@ func (h *Handler) handleGetExportCSV(w http.ResponseWriter, r *http.Request) { if err = h.API.ExportCSV(r.Context(), index, frame, view, slice, w); err != nil { switch err { case ErrFragmentNotFound: - http.Error(w, err.Error(), http.StatusNotFound) + break case ErrClusterDoesNotOwnSlice: http.Error(w, err.Error(), http.StatusPreconditionFailed) default: @@ -1528,26 +1535,30 @@ func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Reque return } - err = h.API.CreateInputDefinition(r.Context(), indexName, inputDefName, req) - switch err { - case nil: - break - case ErrIndexNotFound: - http.Error(w, err.Error(), http.StatusNotFound) - case ErrInputDefinitionExists: - http.Error(w, err.Error(), http.StatusConflict) - case ErrInputDefinitionAttrsRequired: - fallthrough - case ErrInputDefinitionNameRequired: - fallthrough - case ErrInputDefinitionActionRequired: - fallthrough - case ErrInputDefinitionHasPrimaryKey: - fallthrough - case ErrInputDefinitionDupePrimaryKey: - http.Error(w, err.Error(), http.StatusBadRequest) - default: - http.Error(w, err.Error(), http.StatusInternalServerError) + if err = h.API.CreateInputDefinition(r.Context(), indexName, inputDefName, req); err != nil { + switch err { + case ErrIndexNotFound: + http.Error(w, err.Error(), http.StatusNotFound) + case ErrInputDefinitionExists: + http.Error(w, err.Error(), http.StatusConflict) + case ErrInputDefinitionAttrsRequired: + fallthrough + case ErrInputDefinitionNameRequired: + fallthrough + case ErrInputDefinitionActionRequired: + fallthrough + case ErrInputDefinitionHasPrimaryKey: + fallthrough + case ErrInputDefinitionDupePrimaryKey: + http.Error(w, err.Error(), http.StatusBadRequest) + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + + if err := json.NewEncoder(w).Encode(defaultInputDefinitionResponse{}); err != nil { + h.logger().Printf("response encoding error: %s", err) } } diff --git a/handler_test.go b/handler_test.go index 1e30f41ad..39bcc7fff 100644 --- a/handler_test.go +++ b/handler_test.go @@ -65,8 +65,8 @@ func TestHandler_NotFound(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/no_such_path", nil)) @@ -100,8 +100,8 @@ func TestHandler_Schema(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/schema", nil)) if w.Code != http.StatusOK { @@ -139,9 +139,9 @@ func TestHandler_Status(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) - h.StatusHandler = s + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.StatusHandler = s s.Handler = h w := httptest.NewRecorder() @@ -186,8 +186,8 @@ func TestHandler_MaxSlices(t *testing.T) { hldr.MustCreateFragmentIfNotExists("i1", "f1", pilosa.ViewStandard, 0).MustSetBits(40, (0*SliceWidth)+8) h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/slices/max", nil)) if w.Code != http.StatusOK { @@ -227,8 +227,8 @@ func TestHandler_MaxSlices_Inverse(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/slices/max?inverse=true", nil)) if w.Code != http.StatusOK { @@ -244,8 +244,8 @@ func TestHandler_Query_Args_URL(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { if index != "idx0" { t.Fatalf("unexpected index: %s", index) @@ -272,8 +272,8 @@ func TestHandler_Query_Args_Protobuf(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { if index != "idx0" { t.Fatalf("unexpected index: %s", index) @@ -312,8 +312,8 @@ func TestHandler_Query_Args_Err(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=a,b", strings.NewReader("Bitmap(id=100)"))) if w.Code != http.StatusBadRequest { @@ -339,8 +339,8 @@ func TestHandler_Query_Uint64_JSON(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { return []interface{}{uint64(100)}, nil } @@ -360,8 +360,8 @@ func TestHandler_Query_Uint64_Protobuf(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { return []interface{}{uint64(100)}, nil } @@ -390,8 +390,8 @@ func TestHandler_Query_Bitmap_JSON(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { bm := pilosa.NewBitmap(1, 3, 66, pilosa.SliceWidth+1) bm.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} @@ -423,8 +423,8 @@ func TestHandler_Query_Bitmap_ColumnAttrs_JSON(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { bm := pilosa.NewBitmap(1, 3, 66, pilosa.SliceWidth+1) bm.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} @@ -446,8 +446,8 @@ func TestHandler_Query_Bitmap_Protobuf(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { bm := pilosa.NewBitmap(1, pilosa.SliceWidth+1) bm.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} @@ -494,8 +494,8 @@ func TestHandler_Query_Bitmap_ColumnAttrs_Protobuf(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { bm := pilosa.NewBitmap(1, pilosa.SliceWidth+1) bm.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} @@ -555,8 +555,8 @@ func TestHandler_Query_Pairs_JSON(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { return []interface{}{[]pilosa.Pair{ {ID: 1, Count: 2}, @@ -579,8 +579,8 @@ func TestHandler_Query_Pairs_Protobuf(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { return []interface{}{[]pilosa.Pair{ {ID: 1, Count: 2}, @@ -612,15 +612,15 @@ func TestHandler_Query_Err_JSON(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { return nil, errors.New("marker") } w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`Bitmap(id=100)`))) - if w.Code != http.StatusInternalServerError { + if w.Code != http.StatusBadRequest { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{"error":"marker"}`+"\n" { t.Fatalf("unexpected body: %q", body) @@ -633,8 +633,8 @@ func TestHandler_Query_Err_Protobuf(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { return nil, errors.New("marker") } @@ -643,7 +643,7 @@ func TestHandler_Query_Err_Protobuf(t *testing.T) { r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(frame=x, n=2)`)) r.Header.Set("Accept", "application/x-protobuf") h.ServeHTTP(w, r) - if w.Code != http.StatusInternalServerError { + if w.Code != http.StatusBadRequest { t.Fatalf("unexpected status code: %d", w.Code) } @@ -661,8 +661,8 @@ func TestHandler_Query_MethodNotAllowed(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/i/query", nil)) if w.Code != http.StatusMethodNotAllowed { @@ -676,8 +676,8 @@ func TestHandler_Query_ErrParse(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("bad_fn("))) if w.Code != http.StatusBadRequest { @@ -693,7 +693,7 @@ func TestHandler_Index_Delete(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() // Create index. @@ -733,8 +733,8 @@ func TestHandler_DeleteFrame(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/frame/f1", strings.NewReader(""))) if w.Code != http.StatusOK { @@ -753,8 +753,8 @@ func TestHandler_SetIndexTimeQuantum(t *testing.T) { hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("PATCH", "/index/i0/time-quantum", strings.NewReader(`{"timeQuantum":"ymdh"}`))) if w.Code != http.StatusOK { @@ -777,8 +777,8 @@ func TestHandler_SetFrameTimeQuantum(t *testing.T) { } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("PATCH", "/index/i0/frame/f1/time-quantum", strings.NewReader(`{"timeQuantum":"ymdh"}`))) if w.Code != http.StatusOK { @@ -796,7 +796,7 @@ func TestHandler_Index_AttrStore_Diff(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() // Set attributes on the index. @@ -845,7 +845,7 @@ func TestHandler_Frame_AttrStore_Diff(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() // Set attributes on the index. @@ -895,7 +895,7 @@ func TestHandler_Frame_AddField(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() t.Run("OK", func(t *testing.T) { @@ -999,7 +999,7 @@ func TestHandler_Frame_DeleteField(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() t.Run("OK", func(t *testing.T) { @@ -1064,7 +1064,7 @@ func TestHandler_Frame_GetFields(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() t.Run("OK", func(t *testing.T) { @@ -1132,7 +1132,7 @@ func TestHandler_Fragment_BackupRestore(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() // Set bits in the index. @@ -1181,8 +1181,8 @@ func TestHandler_Version(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder w := httptest.NewRecorder() r := test.MustNewHTTPRequest("GET", "/version", nil) @@ -1204,9 +1204,9 @@ func TestHandler_Fragment_Nodes(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(3) - h.Cluster.ReplicaN = 2 + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(3) + h.API.Cluster.ReplicaN = 2 w := httptest.NewRecorder() r := test.MustNewHTTPRequest("GET", "/fragment/nodes?index=X&slice=0", nil) @@ -1233,8 +1233,8 @@ func TestHandler_Expvars(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Cluster = test.NewCluster(1) - h.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder w := httptest.NewRecorder() r := test.MustNewHTTPRequest("GET", "/debug/vars", nil) h.ServeHTTP(w, r) @@ -1279,8 +1279,8 @@ func TestHandler_CreateInputDefinition(t *testing.T) { ] }`) h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/input-definition/input1", bytes.NewBuffer(inputBody))) if w.Code != http.StatusOK { @@ -1314,8 +1314,8 @@ func TestHandler_DuplicatePrimaryKey(t *testing.T) { defer hldr.Close() hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) //Ensure throwing error if there's duplicated primaryKey field invalidPrimaryKey := []byte(` @@ -1419,8 +1419,8 @@ func TestHandler_DeleteInputDefinition(t *testing.T) { hldr := test.MustOpenHolder() defer hldr.Close() h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) // Test index not found. w := httptest.NewRecorder() @@ -1467,8 +1467,8 @@ func TestHandler_GetInputDefinition(t *testing.T) { hldr := test.MustOpenHolder() defer hldr.Close() h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) frames := internal.Frame{Name: "f", Meta: &internal.FrameMeta{RowLabel: "row"}} action := internal.InputDefinitionAction{Frame: "f", ValueDestination: "mapping", ValueMap: map[string]uint64{"Green": 1}} @@ -1634,8 +1634,8 @@ func TestHandler_CreateInput(t *testing.T) { "null_value": null }]`) h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) // Return error if index does not exist. w := httptest.NewRecorder() @@ -1749,8 +1749,8 @@ func TestInput_JSON(t *testing.T) { err: "set-timestamp value must be in time format: YYYY-MM-DD, has: 12345"}, } h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) for _, req := range tests { w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/input/input1", bytes.NewBuffer([]byte(req.json)))) @@ -1810,8 +1810,8 @@ func TestHandler_DeleteView(t *testing.T) { hldr.Index("i0").Frame("f0").SetTimeQuantum("YMD") h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/frame/f0/view/standard_2017", strings.NewReader(""))) if w.Code != http.StatusOK { @@ -1836,8 +1836,8 @@ func TestHandler_RecalculateCaches(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/recalculate-caches", nil)) diff --git a/holder_test.go b/holder_test.go index 41ede1adf..4c63e6e76 100644 --- a/holder_test.go +++ b/holder_test.go @@ -393,7 +393,7 @@ func TestHolderSyncer_SyncHolder(t *testing.T) { defer hldr1.Close() s := test.NewServer() defer s.Close() - s.Handler.Holder = hldr1.Holder + s.Handler.API.Holder = hldr1.Holder s.Handler.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { e := pilosa.NewExecutor(client) e.Holder = hldr1.Holder diff --git a/stats_test.go b/stats_test.go index 60deae993..591236268 100644 --- a/stats_test.go +++ b/stats_test.go @@ -215,10 +215,10 @@ func TestStatsCount_CreateIndex(t *testing.T) { hldr := test.MustOpenHolder() defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() called := false - s.Handler.Holder.Stats = &MockStats{ + s.Handler.API.Holder.Stats = &MockStats{ mockCount: func(name string, value int64, rate float64) { if name != "createIndex" { t.Errorf("Expected createIndex, Results %s", name) @@ -239,7 +239,7 @@ func TestStatsCount_DeleteIndex(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() // Create index. @@ -247,7 +247,7 @@ func TestStatsCount_DeleteIndex(t *testing.T) { t.Fatal(err) } called := false - s.Handler.Holder.Stats = &MockStats{ + s.Handler.API.Holder.Stats = &MockStats{ mockCount: func(name string, value int64, rate float64) { if name != "deleteIndex" { t.Errorf("Expected deleteIndex, Results %s", name) @@ -268,7 +268,7 @@ func TestStatsCount_CreateFrame(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() // Create index. @@ -276,7 +276,7 @@ func TestStatsCount_CreateFrame(t *testing.T) { t.Fatal(err) } called := false - s.Handler.Holder.Stats = &MockStats{ + s.Handler.API.Holder.Stats = &MockStats{ mockCountWithTags: func(name string, value int64, rate float64, index []string) { if name != "createFrame" { t.Errorf("Expected createFrame, Results %s", name) @@ -300,7 +300,7 @@ func TestStatsCount_DeleteFrame(t *testing.T) { defer hldr.Close() s := test.NewServer() - s.Handler.Holder = hldr.Holder + s.Handler.API.Holder = hldr.Holder defer s.Close() called := false // Create index. @@ -308,7 +308,7 @@ func TestStatsCount_DeleteFrame(t *testing.T) { if _, err := indx.CreateFrameIfNotExists("test", pilosa.FrameOptions{}); err != nil { t.Fatal(err) } - s.Handler.Holder.Stats = &MockStats{ + s.Handler.API.Holder.Stats = &MockStats{ mockCountWithTags: func(name string, value int64, rate float64, index []string) { if name != "deleteFrame" { t.Errorf("Expected deleteFrame, Results %s", name) diff --git a/test/handler.go b/test/handler.go index 0f8c1cefd..ae7605603 100644 --- a/test/handler.go +++ b/test/handler.go @@ -27,6 +27,7 @@ import ( "github.com/pilosa/pilosa" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" + "github.com/pkg/errors" ) // Handler represents a test wrapper for pilosa.Handler. @@ -40,10 +41,12 @@ func NewHandler() *Handler { h := &Handler{ Handler: pilosa.NewHandler(), } - h.Handler.Executor = &h.Executor + h.API = pilosa.NewAPI(nil) + h.Handler.API = pilosa.NewAPI(nil) + h.Handler.API.Executor = &h.Executor // Handler test messages can no-op. - h.Broadcaster = pilosa.NopBroadcaster + h.API.Broadcaster = pilosa.NopBroadcaster h.SetNormal() @@ -80,21 +83,35 @@ func NewServer() *Server { if err != nil { panic(err) } + s.Handler.API.URI = uri // Handler test messages can no-op. - s.Handler.Broadcaster = pilosa.NopBroadcaster + s.Handler.API.Broadcaster = pilosa.NopBroadcaster // Create a default cluster on the handler - s.Handler.Cluster = NewCluster(1) - s.Handler.Cluster.Nodes[0].URI = *uri - - s.Handler.Node = s.Handler.Cluster.Nodes[0] + s.Handler.API.Cluster = NewCluster(1) + s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() return s } // LocalStatus exists so that test.Server implements StatusHandler. func (s *Server) LocalStatus() (proto.Message, error) { - return nil, nil + if s.Handler.API.Holder == nil { + return nil, errors.New("Server.Holder is nil") + } + + ns := internal.NodeStatus{ + Host: s.Handler.Handler.API.URI.HostPort(), + State: pilosa.NodeStateUp, + Indexes: pilosa.EncodeIndexes(s.Handler.API.Holder.Indexes()), + } + + // Append Slice list per this Node's indexes + for _, index := range ns.Indexes { + index.Slices = s.Handler.API.Cluster.OwnsSlices(index.Name, index.MaxSlice, s.Handler.API.URI.HostPort()) + } + + return &ns, nil } // ClusterStatus exists so that test.Server implements StatusHandler. From be4965d962dcbf216dfb01a9334ca76b878ae66b Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Tue, 3 Apr 2018 16:09:11 -0700 Subject: [PATCH 07/12] fixup logger, handler.FileSystem --- api.go | 46 ++++++++++++++++++++++------------------------ handler.go | 5 ++--- server.go | 3 ++- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/api.go b/api.go index 97d7828e5..54dd65244 100644 --- a/api.go +++ b/api.go @@ -19,8 +19,6 @@ import ( "encoding/csv" "fmt" "io" - "io/ioutil" - "log" "net/http" "reflect" "strconv" @@ -44,15 +42,15 @@ type API struct { Cluster *Cluster URI *URI RemoteClient *http.Client - logger *log.Logger + Logger Logger } -func NewAPI(logger *log.Logger) *API { - if logger == nil { - logger = log.New(ioutil.Discard, "", 0) - } +func NewAPI() *API { return &API{ - logger: logger, + Broadcaster: NopBroadcaster, + //BroadcastHandler: NopBroadcastHandler, // TODO: implement the nop + //StatusHandler: NopStatusHandler, // TODO: implement the nop + Logger: NopLogger, } } @@ -132,7 +130,7 @@ func (api *API) CreateIndex(ctx context.Context, indexName string, options Index Meta: options.Encode(), }) if err != nil { - api.logger.Printf("problem sending CreateIndex message: %s", err) + api.Logger.Printf("problem sending CreateIndex message: %s", err) return nil, err } api.Holder.Stats.Count("createIndex", 1, 1.0) @@ -159,7 +157,7 @@ func (api *API) DeleteIndex(ctx context.Context, indexName string) error { Index: indexName, }) if err != nil { - api.logger.Printf("problem sending DeleteIndex message: %s", err) + api.Logger.Printf("problem sending DeleteIndex message: %s", err) return err } api.Holder.Stats.Count("deleteIndex", 1, 1.0) @@ -187,7 +185,7 @@ func (api *API) CreateFrame(ctx context.Context, indexName string, frameName str Meta: options.Encode(), }) if err != nil { - api.logger.Printf("problem sending CreateFrame message: %s", err) + api.Logger.Printf("problem sending CreateFrame message: %s", err) return nil, err } api.Holder.Stats.CountWithCustomTags("createFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) @@ -213,7 +211,7 @@ func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName str Frame: frameName, }) if err != nil { - api.logger.Printf("problem sending DeleteFrame message: %s", err) + api.Logger.Printf("problem sending DeleteFrame message: %s", err) return err } api.Holder.Stats.CountWithCustomTags("deleteFrame", 1, 1.0, []string{fmt.Sprintf("index:%s", indexName)}) @@ -223,7 +221,7 @@ func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName str func (api *API) ExportCSV(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, w io.Writer) error { // Validate that this handler owns the slice. if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { - api.logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) + api.Logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) return ErrClusterDoesNotOwnSlice } @@ -413,7 +411,7 @@ func (api *API) CreateInputDefinition(ctx context.Context, indexName string, inp Definition: def, }) if err != nil { - api.logger.Printf("problem sending CreateInputDefinition message: %s", err) + api.Logger.Printf("problem sending CreateInputDefinition message: %s", err) } return nil } @@ -450,7 +448,7 @@ func (api *API) DeleteInputDefinition(ctx context.Context, indexName string, inp Name: inputDefName, }) if err != nil { - api.logger.Printf("problem sending DeleteInputDefinition message: %s", err) + api.Logger.Printf("problem sending DeleteInputDefinition message: %s", err) } return nil } @@ -521,7 +519,7 @@ func (api *API) CreateFrameField(ctx context.Context, indexName string, frameNam Field: encodeField(field), }) if err != nil { - api.logger.Printf("problem sending CreateField message: %s", err) + api.Logger.Printf("problem sending CreateField message: %s", err) } return err } @@ -546,7 +544,7 @@ func (api *API) DeleteFrameField(ctx context.Context, indexName string, frameNam Field: fieldName, }) if err != nil { - api.logger.Printf("problem sending DeleteField message: %s", err) + api.Logger.Printf("problem sending DeleteField message: %s", err) } return err } @@ -600,7 +598,7 @@ func (api *API) DeleteView(ctx context.Context, indexName string, frameName stri View: viewName, }) if err != nil { - api.logger.Printf("problem sending DeleteView message: %s", err) + api.Logger.Printf("problem sending DeleteView message: %s", err) } return err @@ -685,7 +683,7 @@ func (api *API) Import(ctx context.Context, req internal.ImportRequest) error { // Import into fragment. err = frame.Import(req.RowIDs, req.ColumnIDs, timestamps) if err != nil { - api.logger.Printf("import error: index=%s, frame=%s, slice=%d, bits=%d, err=%s", req.Index, req.Frame, req.Slice, len(req.ColumnIDs), err) + api.Logger.Printf("import error: index=%s, frame=%s, slice=%d, bits=%d, err=%s", req.Index, req.Frame, req.Slice, len(req.ColumnIDs), err) } return err } @@ -699,7 +697,7 @@ func (api *API) ImportValue(ctx context.Context, req internal.ImportValueRequest // Import into fragment. err = frame.ImportValue(req.Field, req.ColumnIDs, req.Values) if err != nil { - api.logger.Printf("import error: index=%s, frame=%s, slice=%d, field=%s, bits=%d, err=%s", req.Index, req.Frame, req.Slice, req.Field, len(req.ColumnIDs), err) + api.Logger.Printf("import error: index=%s, frame=%s, slice=%d, field=%s, bits=%d, err=%s", req.Index, req.Frame, req.Slice, req.Field, len(req.ColumnIDs), err) } return err } @@ -750,22 +748,22 @@ func (api *API) ClusterLongQueryTime() time.Duration { func (api *API) indexFrame(indexName string, frameName string, slice uint64) (*Index, *Frame, error) { // Validate that this handler owns the slice. if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { - api.logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) + api.Logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) return nil, nil, ErrClusterDoesNotOwnSlice } // Find the Index. - api.logger.Println("importing:", indexName, frameName, slice) + api.Logger.Printf("importing: %v %v %v", indexName, frameName, slice) index := api.Holder.Index(indexName) if index == nil { - api.logger.Printf("fragment error: index=%s, frame=%s, slice=%d, err=%s", indexName, frameName, slice, ErrIndexNotFound.Error()) + api.Logger.Printf("fragment error: index=%s, frame=%s, slice=%d, err=%s", indexName, frameName, slice, ErrIndexNotFound.Error()) return nil, nil, ErrIndexNotFound } // Retrieve frame. frame := index.Frame(frameName) if frame == nil { - api.logger.Printf("frame error: index=%s, frame=%s, slice=%d, err=%s", indexName, frameName, slice, ErrFrameNotFound.Error()) + api.Logger.Printf("frame error: index=%s, frame=%s, slice=%d, err=%s", indexName, frameName, slice, ErrFrameNotFound.Error()) return nil, nil, ErrFrameNotFound } return index, frame, nil diff --git a/handler.go b/handler.go index f6a385ccb..6e87bb4be 100644 --- a/handler.go +++ b/handler.go @@ -45,6 +45,8 @@ import ( type Handler struct { Router *mux.Router + FileSystem FileSystem + // The execution engine for running queries. Executor interface { Execute(context context.Context, index string, query *pql.Query, slices []uint64, opt *ExecOptions) ([]interface{}, error) @@ -78,9 +80,6 @@ type errorResponse struct { // NewHandler returns a new instance of Handler with a default logger. func NewHandler() *Handler { handler := &Handler{ - Broadcaster: NopBroadcaster, - //BroadcastHandler: NopBroadcastHandler, // TODO: implement the nop - //StatusHandler: NopStatusHandler, // TODO: implement the nop FileSystem: NopFileSystem, Logger: NopLogger, } diff --git a/server.go b/server.go index 338183cf7..54f68a2f2 100644 --- a/server.go +++ b/server.go @@ -115,13 +115,14 @@ func NewServer() *Server { Logger: NopLogger, } - s.Handler.API = NewAPI(s.logger) + s.Handler.API = NewAPI() s.Handler.API.Holder = s.Holder return s } // Open opens and initializes the server. func (s *Server) Open() error { + s.Handler.API.Logger = s.Logger // TODO do this in NewServer with functional options s.Logger.Printf("open server") // s.ln can be configured prior to Open() via s.OpenListener(). if s.ln == nil { From f8cb579187ab6730a6b31b2c588ce993b7431565 Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Mon, 9 Apr 2018 14:29:43 -0500 Subject: [PATCH 08/12] get tests passing --- api.go | 79 ++++++++++-- client_test.go | 2 +- cluster.go | 6 +- ctl/backup_test.go | 2 +- ctl/export_test.go | 2 +- ctl/import_test.go | 4 +- ctl/restore_test.go | 2 +- handler.go | 272 ++++++++++++++++++----------------------- handler_test.go | 11 +- pilosa.go | 4 + server/cluster_test.go | 14 +-- server/server.go | 1 + server/server_test.go | 6 +- test/handler.go | 24 +--- 14 files changed, 222 insertions(+), 207 deletions(-) diff --git a/api.go b/api.go index 54dd65244..82ab3a4ce 100644 --- a/api.go +++ b/api.go @@ -28,6 +28,7 @@ import ( "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" + "github.com/pkg/errors" ) type API struct { @@ -40,7 +41,7 @@ type API struct { BroadcastHandler BroadcastHandler StatusHandler StatusHandler Cluster *Cluster - URI *URI + URI URI RemoteClient *http.Client Logger Logger } @@ -220,7 +221,7 @@ func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName str func (api *API) ExportCSV(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, w io.Writer) error { // Validate that this handler owns the slice. - if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { + if !api.Cluster.OwnsFragment(api.LocalID(), indexName, slice) { api.Logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) return ErrClusterDoesNotOwnSlice } @@ -339,7 +340,7 @@ func (api *API) RestoreFrame(ctx context.Context, indexName string, frameName st // Loop over each slice and import it if this node owns it. for slice := uint64(0); slice <= maxSlices[indexName]; slice++ { // Ignore this slice if we don't own it. - if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { + if !api.Cluster.OwnsFragment(api.LocalID(), indexName, slice) { continue } @@ -475,8 +476,13 @@ func (api *API) WriteInput(ctx context.Context, indexName string, inputDefName s return nil } -func (api *API) RecalculateCaches(ctx context.Context) { +func (api *API) RecalculateCaches(ctx context.Context) error { + err := api.Broadcaster.SendSync(&internal.RecalculateCaches{}) + if err != nil { + return errors.Wrap(err, "broacasting message") + } api.Holder.RecalculateCaches() + return nil } func (api *API) PostClusterMessage(ctx context.Context, pb proto.Message) error { @@ -487,7 +493,7 @@ func (api *API) PostClusterMessage(ctx context.Context, pb proto.Message) error return nil } -func (api *API) LocalID(ctx context.Context) string { +func (api *API) LocalID() string { return api.Cluster.Node.ID } @@ -549,7 +555,7 @@ func (api *API) DeleteFrameField(ctx context.Context, indexName string, frameNam return err } -func (api *API) FrameFields(ctx context.Context, indexName string, frameName string) (*FrameSchema, error) { +func (api *API) FrameFields(ctx context.Context, indexName string, frameName string) ([]*Field, error) { index := api.Holder.index(indexName) if index == nil { return nil, ErrIndexNotFound @@ -724,13 +730,14 @@ func (api *API) ModifyFrameTimeQuantum(ctx context.Context, indexName string, fr return frame.SetTimeQuantum(timeQuantum) } -func (api *API) SliceMax(ctx context.Context, inverse bool) map[string]uint64 { - if inverse { - return api.Holder.MaxInverseSlices() - } +func (api *API) MaxSlices(ctx context.Context) map[string]uint64 { return api.Holder.MaxSlices() } +func (api *API) MaxInverseSlices(ctx context.Context) map[string]uint64 { + return api.Holder.MaxInverseSlices() +} + func (api *API) StatsWithTags(tags []string) StatsClient { if api.Holder == nil || api.Cluster == nil { return nil @@ -747,7 +754,7 @@ func (api *API) ClusterLongQueryTime() time.Duration { func (api *API) indexFrame(indexName string, frameName string, slice uint64) (*Index, *Frame, error) { // Validate that this handler owns the slice. - if !api.Cluster.OwnsFragment(api.URI.HostPort(), indexName, slice) { + if !api.Cluster.OwnsFragment(api.LocalID(), indexName, slice) { api.Logger.Printf("host does not own slice %s-%s slice:%d", api.URI, indexName, slice) return nil, nil, ErrClusterDoesNotOwnSlice } @@ -837,3 +844,53 @@ func (api *API) inputJSONDataParser(req map[string]interface{}, index *Index, na } return setBits, nil } + +func (api *API) SetCoordinator(ctx context.Context, id string) (oldNode, newNode *Node, err error) { + oldNode = api.Cluster.nodeByID(api.Cluster.Coordinator) + newNode = api.Cluster.nodeByID(id) + if newNode == nil { + return nil, nil, errors.Wrap(ErrNodeIDNotExists, "getting new node") + } + + // If the new coordinator is this node, do the SetCoordinator directly. + if newNode.ID == api.LocalID() { + return oldNode, newNode, api.Cluster.SetCoordinator(newNode) + } + + // Send the set-coordinator message to new node. + err = api.Broadcaster.SendTo( + newNode, + &internal.SetCoordinatorMessage{ + New: EncodeNode(newNode), + }) + if err != nil { + return nil, nil, fmt.Errorf("problem sending SetCoordinator message: %s", err) + } + return oldNode, newNode, nil +} + +func (api *API) RemoveNode(id string) (*Node, error) { + removeNode := api.Cluster.nodeByID(id) + if removeNode == nil { + return nil, errors.Wrap(ErrNodeIDNotExists, "finding node to remove") + } + + // Start the resize process (similar to NodeJoin) + err := api.Cluster.NodeLeave(removeNode) + if err != nil { + return removeNode, errors.Wrap(err, "calling node leave") + } + return removeNode, nil +} + +func (api *API) ResizeAbort() error { + if !api.Cluster.IsCoordinator() { + return ErrNodeNotCoordinator + } + err := api.Cluster.CompleteCurrentJob(ResizeJobStateAborted) + return errors.Wrap(err, "complete current job") +} + +func (api *API) State() string { + return api.Cluster.State() +} diff --git a/client_test.go b/client_test.go index 36b9dc38d..cdbaba788 100644 --- a/client_test.go +++ b/client_test.go @@ -86,7 +86,7 @@ func TestClient_MultiNode(t *testing.T) { // Create a dispersed set of bitmaps across 3 nodes such that each individual node and slice width increment would reveal a different TopN. sliceNums := []uint64{1, 2, 6} for i, num := range sliceNums { - owns := s[i].Handler.Handler.API.Cluster.OwnsSlices("i", 20, s[i].Host()) + owns := s[i].Handler.Handler.API.Cluster.OwnsSlices("i", 20, s[i].HostURI()) ownsNum := false for _, ownNum := range owns { if ownNum == num { diff --git a/cluster.go b/cluster.go index 7e53628ac..c490c58cb 100644 --- a/cluster.go +++ b/cluster.go @@ -1198,7 +1198,7 @@ func (c *Cluster) CompleteCurrentJob(state string) error { c.mu.Lock() defer c.mu.Unlock() if c.currentJob == nil { - return fmt.Errorf("no resize job currently running") + return ErrResizeNotRunning } c.currentJob.SetState(state) c.currentJob = nil @@ -1747,7 +1747,7 @@ func (c *Cluster) nodeJoin(node *Node) error { func (c *Cluster) NodeLeave(node *Node) error { // Refuse the request if this is not the coordinator. if !c.IsCoordinator() { - return fmt.Errorf("Node removal requests are only valid on the Coordinator node: %s", c.CoordinatorNode().ID) + return fmt.Errorf("node removal requests are only valid on the coordinator node: %s", c.CoordinatorNode().ID) } if c.State() != ClusterStateNormal { @@ -1761,7 +1761,7 @@ func (c *Cluster) NodeLeave(node *Node) error { // Prevent removing the coordinator node (this node). if node.ID == c.Node.ID { - return fmt.Errorf("The coordinator node cannot be removed. First, make a different node the new coordinator.") + return fmt.Errorf("coordinator cannot be removed; first, make a different node the new coordinator.") } // See if resize job can be generated diff --git a/ctl/backup_test.go b/ctl/backup_test.go index 7a4feb3d9..d80b72475 100644 --- a/ctl/backup_test.go +++ b/ctl/backup_test.go @@ -50,7 +50,7 @@ func TestBackupCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - s.Handler.API.URI = uri + s.Handler.API.URI = *uri s.Handler.API.Cluster = test.NewCluster(1) s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() s.Handler.API.Holder = hldr.Holder diff --git a/ctl/export_test.go b/ctl/export_test.go index 2bcccd6ba..b414f711f 100644 --- a/ctl/export_test.go +++ b/ctl/export_test.go @@ -63,7 +63,7 @@ func TestExportCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - s.Handler.API.URI = uri + s.Handler.API.URI = *uri s.Handler.API.Cluster = test.NewCluster(1) s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() s.Handler.API.Holder = hldr.Holder diff --git a/ctl/import_test.go b/ctl/import_test.go index 119a56071..bd259e342 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -69,7 +69,7 @@ func TestImportCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - s.Handler.API.URI = uri + s.Handler.API.URI = *uri s.Handler.API.Cluster = test.NewCluster(1) s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() s.Handler.API.Holder = hldr.Holder @@ -110,7 +110,7 @@ func TestImportCommand_RunValue(t *testing.T) { t.Fatal(err) } - s.Handler.API.URI = uri + s.Handler.API.URI = *uri s.Handler.API.Cluster = test.NewCluster(1) s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() s.Handler.API.Holder = hldr.Holder diff --git a/ctl/restore_test.go b/ctl/restore_test.go index f1edb7fe9..04cb30415 100644 --- a/ctl/restore_test.go +++ b/ctl/restore_test.go @@ -52,7 +52,7 @@ func TestRestoreCommand_Run(t *testing.T) { if err != nil { t.Fatal(err) } - s.Handler.API.URI = uri + s.Handler.API.URI = *uri s.Handler.API.Cluster = test.NewCluster(1) s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() s.Handler.API.Holder = hldr.Holder diff --git a/handler.go b/handler.go index 6e87bb4be..3ac7f6e94 100644 --- a/handler.go +++ b/handler.go @@ -17,7 +17,6 @@ package pilosa import ( "context" "encoding/json" - "errors" "expvar" "fmt" "io" @@ -26,26 +25,27 @@ import ( "net/url" // Imported for its side-effect of registering pprof endpoints with the server. _ "net/http/pprof" + "reflect" "runtime/debug" "strconv" "strings" "time" - - "reflect" + "unicode" "github.com/gogo/protobuf/proto" "github.com/gorilla/mux" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" - - "unicode" + "github.com/pkg/errors" ) // Handler represents an HTTP handler. type Handler struct { Router *mux.Router - FileSystem FileSystem + FileSystem FileSystem + NormalRouter *mux.Router + RestrictedRouter *mux.Router // The execution engine for running queries. Executor interface { @@ -215,7 +215,7 @@ func loadNormal(router *mux.Router, handler *Handler) { } func (h *Handler) reportRestricted(w http.ResponseWriter, r *http.Request) { - http.Error(w, fmt.Sprintf("not allowed in cluster state %s", h.Cluster.State()), http.StatusMethodNotAllowed) + http.Error(w, fmt.Sprintf("not allowed in cluster state %s", h.API.State()), http.StatusMethodNotAllowed) } func (h *Handler) methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) { @@ -295,7 +295,10 @@ func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { return } - cs := pb.(*internal.ClusterStatus) + cs, ok := status.(*internal.ClusterStatus) + if !ok { + panic("status is not a status") + } if err := json.NewEncoder(w).Encode(getStatusResponse{ State: cs.State, Nodes: DecodeNodes(cs.Nodes), @@ -348,28 +351,13 @@ func (h *Handler) handlePostQuery(w http.ResponseWriter, r *http.Request) { } } -func (h *Handler) handleGetSliceMax(w http.ResponseWriter, r *http.Request) { - var err error - inverse := false - inverseStr := r.URL.Query().Get("inverse") - if inverseStr != "" { - inverse, err = strconv.ParseBool(r.URL.Query().Get("inverse")) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - } - ms := h.API.SliceMax(r.Context(), inverse) - if strings.Contains(r.Header.Get("Accept"), "application/x-protobuf") { - pb := &internal.MaxSlicesResponse{ - MaxSlices: ms, - } - if buf, err := proto.Marshal(pb); err != nil { - h.Logger.Printf("protobuf marshal error: %s", err) - } else if _, err := w.Write(buf); err != nil { - h.Logger.Printf("stream write error: %s", err) - } - return +// handleGetSlicesMax handles GET /schema requests. +func (h *Handler) handleGetSlicesMax(w http.ResponseWriter, r *http.Request) { + if err := json.NewEncoder(w).Encode(getSlicesMaxResponse{ + Standard: h.API.MaxSlices(r.Context()), + Inverse: h.API.MaxInverseSlices(r.Context()), + }); err != nil { + h.Logger.Printf("write slices-max response error: %s", err) } } @@ -802,7 +790,7 @@ func (h *Handler) handleGetFrameFields(w http.ResponseWriter, r *http.Request) { indexName := mux.Vars(r)["index"] frameName := mux.Vars(r)["frame"] - schema, err := h.API.FrameFields(r.Context(), indexName, frameName) + fields, err := h.API.FrameFields(r.Context(), indexName, frameName) if err != nil { switch err { case ErrIndexNotFound: @@ -1557,7 +1545,7 @@ func (h *Handler) handlePostInputDefinition(w http.ResponseWriter, r *http.Reque } if err := json.NewEncoder(w).Encode(defaultInputDefinitionResponse{}); err != nil { - h.logger().Printf("response encoding error: %s", err) + h.Logger.Printf("response encoding error: %s", err) } } @@ -1646,46 +1634,24 @@ func (h *Handler) handlePostInput(w http.ResponseWriter, r *http.Request) { } } -// <<<<<<< b702f70610962341116d9678ce7c5877b7e171ca -// handlePostClusterResizeSetCoordinator handles POST /cluster/resize/set-coordinator request. func (h *Handler) handlePostClusterResizeSetCoordinator(w http.ResponseWriter, r *http.Request) { // Decode request. var req setCoordinatorRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) + http.Error(w, "decoding request "+err.Error(), http.StatusBadRequest) return } - oldNode := h.Cluster.nodeByID(h.Cluster.Coordinator) - newNode := h.Cluster.nodeByID(req.ID) - if newNode == nil { - http.Error(w, "Node with provided ID does not exist", http.StatusBadRequest) - return - } - - if err := func() error { - // If the new coordinator is this node, do the SetCoordinator directly. - if newNode.ID == h.Node.ID { - return h.Cluster.SetCoordinator(newNode) + oldNode, newNode, err := h.API.SetCoordinator(r.Context(), req.ID) + if err != nil { + if errors.Cause(err) == ErrNodeIDNotExists { + http.Error(w, "setting new coordinator: "+err.Error(), http.StatusNotFound) + } else { + http.Error(w, "setting new coordinator: "+err.Error(), http.StatusInternalServerError) } - - // Send the set-coordinator message to new node. - err := h.Broadcaster.SendTo( - newNode, - &internal.SetCoordinatorMessage{ - New: EncodeNode(newNode), - }) - if err != nil { - return fmt.Errorf("problem sending SetCoordinator message: %s", err) - } - - return nil - }(); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Encode response. if err := json.NewEncoder(w).Encode(setCoordinatorResponse{ Old: oldNode, @@ -1714,16 +1680,13 @@ func (h *Handler) handlePostClusterResizeRemoveNode(w http.ResponseWriter, r *ht return } - removeNode := h.Cluster.nodeByID(req.ID) - if removeNode == nil { - http.Error(w, fmt.Sprintf("Node is not a member of the cluster: %s", req.ID), http.StatusBadRequest) - return - } - - // Start the resize process (similar to NodeJoin) - err = h.Cluster.NodeLeave(removeNode) + removeNode, err := h.API.RemoveNode(req.ID) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + if errors.Cause(err) == ErrNodeIDNotExists { + http.Error(w, "removing node: "+err.Error(), http.StatusNotFound) + } else { + http.Error(w, "removing node: "+err.Error(), http.StatusInternalServerError) + } return } @@ -1745,21 +1708,20 @@ type removeNodeResponse struct { // handlePostClusterResizeAbort handles POST /cluster/resize/abort request. func (h *Handler) handlePostClusterResizeAbort(w http.ResponseWriter, r *http.Request) { + err := h.API.ResizeAbort() var msg string - - if err := func() error { - if !h.Cluster.IsCoordinator() { - return fmt.Errorf("abort requests must be made on the coordinator node") + if err != nil { + switch errors.Cause(err) { + case ErrNodeNotCoordinator: + http.Error(w, err.Error(), http.StatusBadRequest) + return + case ErrResizeNotRunning: + msg = err.Error() + default: + http.Error(w, err.Error(), http.StatusInternalServerError) + return } - err := h.Cluster.CompleteCurrentJob(ResizeJobStateAborted) - if err != nil { - return err - } - return nil - }(); err != nil { - msg = err.Error() } - // Encode response. if err := json.NewEncoder(w).Encode(clusterResizeAbortResponse{ Info: msg, @@ -1772,80 +1734,82 @@ type clusterResizeAbortResponse struct { Info string `json:"info"` } -// InputJSONDataParser validates input json file and executes SetBit. -func (h *Handler) InputJSONDataParser(req map[string]interface{}, index *Index, name string) (map[string][]*Bit, error) { - inputDef, err := index.InputDefinition(name) - if err != nil { - return nil, err - } - // If field in input data is not in defined definition, return error. - var colValue uint64 - validFields := make(map[string]bool) - timestampFrame := make(map[string]int64) - for _, field := range inputDef.Fields() { - validFields[field.Name] = true - if field.PrimaryKey { - value, ok := req[field.Name] - if !ok { - return nil, fmt.Errorf("primary key does not exist") - } - rawValue, ok := value.(float64) // The default JSON marshalling will interpret this as a float - if !ok { - return nil, fmt.Errorf("float64 require, got value:%s, type: %s", value, reflect.TypeOf(value)) - } - colValue = uint64(rawValue) - } - // Find frame that need to add timestamp. - for _, action := range field.Actions { - if action.ValueDestination == InputSetTimestamp { - timestampFrame[action.Frame], err = GetTimeStamp(req, field.Name) - if err != nil { - return nil, err - } - } - } - } +// // InputJSONDataParser validates input json file and executes SetBit. +// func (h *Handler) InputJSONDataParser(req map[string]interface{}, index *Index, name string) (map[string][]*Bit, error) { +// inputDef, err := index.InputDefinition(name) +// if err != nil { +// return nil, err +// } +// // If field in input data is not in defined definition, return error. +// var colValue uint64 +// validFields := make(map[string]bool) +// timestampFrame := make(map[string]int64) +// for _, field := range inputDef.Fields() { +// validFields[field.Name] = true +// if field.PrimaryKey { +// value, ok := req[field.Name] +// if !ok { +// return nil, fmt.Errorf("primary key does not exist") +// } +// rawValue, ok := value.(float64) // The default JSON marshalling will interpret this as a float +// if !ok { +// return nil, fmt.Errorf("float64 require, got value:%s, type: %s", value, reflect.TypeOf(value)) +// } +// colValue = uint64(rawValue) +// } +// // Find frame that need to add timestamp. +// for _, action := range field.Actions { +// if action.ValueDestination == InputSetTimestamp { +// timestampFrame[action.Frame], err = GetTimeStamp(req, field.Name) +// if err != nil { +// return nil, err +// } +// } +// } +// } - for key := range req { - _, ok := validFields[key] - if !ok { - return nil, fmt.Errorf("field not found: %s", key) - } - } +// for key := range req { +// _, ok := validFields[key] +// if !ok { +// return nil, fmt.Errorf("field not found: %s", key) +// } +// } - setBits := make(map[string][]*Bit) +// setBits := make(map[string][]*Bit) - for _, field := range inputDef.Fields() { - // skip field that defined in definition but not in input data - if _, ok := req[field.Name]; !ok { - continue - } +// for _, field := range inputDef.Fields() { +// // skip field that defined in definition but not in input data +// if _, ok := req[field.Name]; !ok { +// continue +// } - // Looking into timestampFrame map and set timestamp to the whole frame - for _, action := range field.Actions { - frame := action.Frame - timestamp := timestampFrame[action.Frame] - // Skip input data field values that are set to null - if req[field.Name] == nil { - continue - } - bit, err := HandleAction(action, req[field.Name], colValue, timestamp) - if err != nil { - return nil, fmt.Errorf("error handling action: %s, err: %s", action.ValueDestination, err) - } - if bit != nil { - setBits[frame] = append(setBits[frame], bit) - } - } - } - return setBits, nil -} - -// ======= -// >>>>>>> Moved more of handler to API +// // Looking into timestampFrame map and set timestamp to the whole frame +// for _, action := range field.Actions { +// frame := action.Frame +// timestamp := timestampFrame[action.Frame] +// // Skip input data field values that are set to null +// if req[field.Name] == nil { +// continue +// } +// bit, err := HandleAction(action, req[field.Name], colValue, timestamp) +// if err != nil { +// return nil, fmt.Errorf("error handling action: %s, err: %s", action.ValueDestination, err) +// } +// if bit != nil { +// setBits[frame] = append(setBits[frame], bit) +// } +// } +// } +// return setBits, nil +// } func (h *Handler) handleRecalculateCaches(w http.ResponseWriter, r *http.Request) { - h.API.RecalculateCaches(r.Context()) + err := h.API.RecalculateCaches(r.Context()) + if err != nil { + http.Error(w, "recalculating caches: "+err.Error(), http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusNoContent) } @@ -1901,7 +1865,7 @@ func (h *Handler) handlePostClusterMessage(w http.ResponseWriter, r *http.Reques } func (h *Handler) handleGetID(w http.ResponseWriter, r *http.Request) { - _, err := w.Write([]byte(h.API.LocalID(r.Context()))) + _, err := w.Write([]byte(h.API.LocalID())) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } @@ -1936,12 +1900,12 @@ func (s *queryValidationSpec) Optional(args ...string) *queryValidationSpec { func (s queryValidationSpec) validate(query url.Values) error { for _, req := range s.required { if query.Get(req) == "" { - return errors.New(fmt.Sprintf("%s is required", req)) + return errors.Errorf("%s is required", req) } } - for k, _ := range query { + for k := range query { if _, ok := s.args[k]; !ok { - return errors.New(fmt.Sprintf("%s is not a valid argument", k)) + return errors.Errorf("%s is not a valid argument", k) } } return nil diff --git a/handler_test.go b/handler_test.go index 39bcc7fff..c9a3ef988 100644 --- a/handler_test.go +++ b/handler_test.go @@ -158,14 +158,15 @@ func TestHandler_ClusterResizeAbort(t *testing.T) { t.Run("No resize job", func(t *testing.T) { h := test.NewHandler() - h.Cluster = test.NewCluster(1) + h.API.Cluster = test.NewCluster(1) h.SetRestricted() w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/cluster/resize/abort", nil)) if w.Code != http.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"info":"no resize job currently running"}`+"\n" { + bod, err := ioutil.ReadAll(w.Body) + t.Fatalf("unexpected status code: %d, bod: %s, readerr: %v", w.Code, bod, err) + } else if body := w.Body.String(); body != `{"info":"complete current job: no resize job currently running"}`+"\n" { t.Fatalf("unexpected body: %s", body) } }) @@ -1852,8 +1853,8 @@ func TestHandler_WebUI(t *testing.T) { defer hldr.Close() h := test.NewHandler() - h.Holder = hldr.Holder - h.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) h.FileSystem = &statik.FileSystem{} w := httptest.NewRecorder() diff --git a/pilosa.go b/pilosa.go index 04c76a133..0c7e790a3 100644 --- a/pilosa.go +++ b/pilosa.go @@ -78,6 +78,10 @@ var ( ErrConfigHostsMissing = errors.New("missing bind address in cluster hosts") ErrClusterDoesNotOwnSlice = errors.New("cluster does not own slice") + + ErrNodeIDNotExists = errors.New("node with provided ID does not exist") + ErrNodeNotCoordinator = errors.New("node is not the coordinator") + ErrResizeNotRunning = errors.New("no resize job currently running") ) // Regular expression to validate index and frame names. diff --git a/server/cluster_test.go b/server/cluster_test.go index 6bbdea4b4..99cd62225 100644 --- a/server/cluster_test.go +++ b/server/cluster_test.go @@ -62,7 +62,7 @@ func TestMain_SendReceiveMessage(t *testing.T) { m0.Server.Cluster.MemberSet = gossipMemberSet0 m0.Server.Broadcaster = m0.Server m0.Server.Gossiper = gossipMemberSet0 - m0.Server.Handler.Broadcaster = m0.Server.Broadcaster + m0.Server.Handler.API.Broadcaster = m0.Server.Broadcaster m0.Server.Holder.Broadcaster = m0.Server.Broadcaster m0.Server.BroadcastReceiver = gossipMemberSet0 @@ -89,7 +89,7 @@ func TestMain_SendReceiveMessage(t *testing.T) { m1.Server.Cluster.MemberSet = gossipMemberSet1 m1.Server.Broadcaster = m1.Server m1.Server.Gossiper = gossipMemberSet1 - m1.Server.Handler.Broadcaster = m1.Server.Broadcaster + m1.Server.Handler.API.Broadcaster = m1.Server.Broadcaster m1.Server.Holder.Broadcaster = m1.Server.Broadcaster m1.Server.BroadcastReceiver = gossipMemberSet1 @@ -507,9 +507,9 @@ func TestClusterResize_RemoveNode(t *testing.T) { t.Run("ErrorRemoveInvalidNode", func(t *testing.T) { resp := test.MustDo("POST", m0.URL()+fmt.Sprintf("/cluster/resize/remove-node"), `{"id": "invalid-node-id"}`) - expBody := "Node is not a member of the cluster: invalid-node-id" - if resp.StatusCode != http.StatusBadRequest { - t.Fatalf("expected StatusCode %d but got %d", http.StatusBadRequest, resp.StatusCode) + expBody := "removing node: finding node to remove: node with provided ID does not exist" + if resp.StatusCode != http.StatusNotFound { + t.Fatalf("expected StatusCode %d but got %d", http.StatusNotFound, resp.StatusCode) } else if strings.TrimSpace(resp.Body) != expBody { t.Fatalf("expected Body '%s' but got '%s'", expBody, strings.TrimSpace(resp.Body)) } @@ -521,7 +521,7 @@ func TestClusterResize_RemoveNode(t *testing.T) { resp = test.MustDo("POST", m0.URL()+fmt.Sprintf("/cluster/resize/remove-node"), fmt.Sprintf(`{"id": "%s"}`, nodeID)) - expBody := "The coordinator node cannot be removed. First, make a different node the new coordinator." + expBody := "removing node: calling node leave: coordinator cannot be removed; first, make a different node the new coordinator." if resp.StatusCode != http.StatusInternalServerError { t.Fatalf("expected StatusCode %d but got %d", http.StatusInternalServerError, resp.StatusCode) } else if strings.TrimSpace(resp.Body) != expBody { @@ -538,7 +538,7 @@ func TestClusterResize_RemoveNode(t *testing.T) { resp = test.MustDo("POST", m1.URL()+fmt.Sprintf("/cluster/resize/remove-node"), fmt.Sprintf(`{"id": "%s"}`, nodeID)) - expBody := fmt.Sprintf("Node removal requests are only valid on the Coordinator node: %s", coordinatorNodeID) + expBody := fmt.Sprintf("removing node: calling node leave: node removal requests are only valid on the coordinator node: %s", coordinatorNodeID) if resp.StatusCode != http.StatusInternalServerError { t.Fatalf("expected StatusCode %d but got %d", http.StatusInternalServerError, resp.StatusCode) } else if strings.TrimSpace(resp.Body) != expBody { diff --git a/server/server.go b/server/server.go index a50ad3e71..62c7a70a5 100644 --- a/server/server.go +++ b/server/server.go @@ -219,6 +219,7 @@ func (m *Command) SetupServer() error { c := pilosa.GetHTTPClient(TLSConfig) m.Server.RemoteClient = c m.Server.Handler.API.RemoteClient = c + m.Server.Cluster.RemoteClient = c // Statik file system. m.Server.Handler.FileSystem = &statik.FileSystem{} diff --git a/server/server_test.go b/server/server_test.go index 3852845a6..9f6cdfa8a 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -409,7 +409,11 @@ func TestMain_RecalculateHashes(t *testing.T) { } // Calculate caches on the first node - cluster[0].RecalculateCaches() + err := cluster[0].RecalculateCaches() + if err != nil { + t.Fatalf("recalculating caches: %v", err) + } + target := `{"results":[[{"id":7,"count":99},{"id":1,"count":99},{"id":9,"count":99},{"id":5,"count":99},{"id":4,"count":99},{"id":8,"count":99},{"id":2,"count":99},{"id":6,"count":99},{"id":3,"count":99}]]}` // Run a TopN query on all nodes. The result should be the same as the target. diff --git a/test/handler.go b/test/handler.go index ae7605603..167187e0b 100644 --- a/test/handler.go +++ b/test/handler.go @@ -27,7 +27,6 @@ import ( "github.com/pilosa/pilosa" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" - "github.com/pkg/errors" ) // Handler represents a test wrapper for pilosa.Handler. @@ -41,8 +40,8 @@ func NewHandler() *Handler { h := &Handler{ Handler: pilosa.NewHandler(), } - h.API = pilosa.NewAPI(nil) - h.Handler.API = pilosa.NewAPI(nil) + h.API = pilosa.NewAPI() + h.Handler.API = pilosa.NewAPI() h.Handler.API.Executor = &h.Executor // Handler test messages can no-op. @@ -83,7 +82,7 @@ func NewServer() *Server { if err != nil { panic(err) } - s.Handler.API.URI = uri + s.Handler.API.URI = *uri // Handler test messages can no-op. s.Handler.API.Broadcaster = pilosa.NopBroadcaster @@ -96,22 +95,7 @@ func NewServer() *Server { // LocalStatus exists so that test.Server implements StatusHandler. func (s *Server) LocalStatus() (proto.Message, error) { - if s.Handler.API.Holder == nil { - return nil, errors.New("Server.Holder is nil") - } - - ns := internal.NodeStatus{ - Host: s.Handler.Handler.API.URI.HostPort(), - State: pilosa.NodeStateUp, - Indexes: pilosa.EncodeIndexes(s.Handler.API.Holder.Indexes()), - } - - // Append Slice list per this Node's indexes - for _, index := range ns.Indexes { - index.Slices = s.Handler.API.Cluster.OwnsSlices(index.Name, index.MaxSlice, s.Handler.API.URI.HostPort()) - } - - return &ns, nil + return nil, nil } // ClusterStatus exists so that test.Server implements StatusHandler. From 96e953e6e07f5c894ece6d4b8b93920552a1efe2 Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Mon, 9 Apr 2018 16:42:50 -0500 Subject: [PATCH 09/12] rename var to be more descriptive --- handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/handler.go b/handler.go index 3ac7f6e94..bb9fbc441 100644 --- a/handler.go +++ b/handler.go @@ -289,13 +289,13 @@ func (h *Handler) handleGetSchema(w http.ResponseWriter, r *http.Request) { // handleGetStatus handles GET /status requests. func (h *Handler) handleGetStatus(w http.ResponseWriter, r *http.Request) { - status, err := h.API.Status(r.Context()) + pb, err := h.API.Status(r.Context()) if err != nil { h.Logger.Printf("cluster status error: %s", err) return } - cs, ok := status.(*internal.ClusterStatus) + cs, ok := pb.(*internal.ClusterStatus) if !ok { panic("status is not a status") } From 401c9682b8bfd0e6983c8d59b4d9e6cf7b17dcf9 Mon Sep 17 00:00:00 2001 From: Matthew Jaffee Date: Tue, 10 Apr 2018 09:40:28 -0500 Subject: [PATCH 10/12] remove commented code and fix double-NewAPI --- handler.go | 69 ------------------------------------------------- test/handler.go | 2 +- 2 files changed, 1 insertion(+), 70 deletions(-) diff --git a/handler.go b/handler.go index bb9fbc441..d7536f738 100644 --- a/handler.go +++ b/handler.go @@ -1734,75 +1734,6 @@ type clusterResizeAbortResponse struct { Info string `json:"info"` } -// // InputJSONDataParser validates input json file and executes SetBit. -// func (h *Handler) InputJSONDataParser(req map[string]interface{}, index *Index, name string) (map[string][]*Bit, error) { -// inputDef, err := index.InputDefinition(name) -// if err != nil { -// return nil, err -// } -// // If field in input data is not in defined definition, return error. -// var colValue uint64 -// validFields := make(map[string]bool) -// timestampFrame := make(map[string]int64) -// for _, field := range inputDef.Fields() { -// validFields[field.Name] = true -// if field.PrimaryKey { -// value, ok := req[field.Name] -// if !ok { -// return nil, fmt.Errorf("primary key does not exist") -// } -// rawValue, ok := value.(float64) // The default JSON marshalling will interpret this as a float -// if !ok { -// return nil, fmt.Errorf("float64 require, got value:%s, type: %s", value, reflect.TypeOf(value)) -// } -// colValue = uint64(rawValue) -// } -// // Find frame that need to add timestamp. -// for _, action := range field.Actions { -// if action.ValueDestination == InputSetTimestamp { -// timestampFrame[action.Frame], err = GetTimeStamp(req, field.Name) -// if err != nil { -// return nil, err -// } -// } -// } -// } - -// for key := range req { -// _, ok := validFields[key] -// if !ok { -// return nil, fmt.Errorf("field not found: %s", key) -// } -// } - -// setBits := make(map[string][]*Bit) - -// for _, field := range inputDef.Fields() { -// // skip field that defined in definition but not in input data -// if _, ok := req[field.Name]; !ok { -// continue -// } - -// // Looking into timestampFrame map and set timestamp to the whole frame -// for _, action := range field.Actions { -// frame := action.Frame -// timestamp := timestampFrame[action.Frame] -// // Skip input data field values that are set to null -// if req[field.Name] == nil { -// continue -// } -// bit, err := HandleAction(action, req[field.Name], colValue, timestamp) -// if err != nil { -// return nil, fmt.Errorf("error handling action: %s, err: %s", action.ValueDestination, err) -// } -// if bit != nil { -// setBits[frame] = append(setBits[frame], bit) -// } -// } -// } -// return setBits, nil -// } - func (h *Handler) handleRecalculateCaches(w http.ResponseWriter, r *http.Request) { err := h.API.RecalculateCaches(r.Context()) if err != nil { diff --git a/test/handler.go b/test/handler.go index 167187e0b..cb90cd383 100644 --- a/test/handler.go +++ b/test/handler.go @@ -41,7 +41,7 @@ func NewHandler() *Handler { Handler: pilosa.NewHandler(), } h.API = pilosa.NewAPI() - h.Handler.API = pilosa.NewAPI() + h.Handler.API = h.API h.Handler.API.Executor = &h.Executor // Handler test messages can no-op. From 3d595e6966f8a8643b36120e52d58b65dac08d22 Mon Sep 17 00:00:00 2001 From: Travis Turner Date: Tue, 10 Apr 2018 15:36:18 -0500 Subject: [PATCH 11/12] make sure that slices is treated as inverseSlices on inverse calls --- executor.go | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/executor.go b/executor.go index 2154b60ff..e74cd63ba 100644 --- a/executor.go +++ b/executor.go @@ -84,29 +84,31 @@ func (e *Executor) Execute(ctx context.Context, index string, q *pql.Query, slic // to send queries to different slices based on orientation. var inverseSlices []uint64 - // If slices aren't specified, then include all of them. - if len(slices) == 0 { - // Determine slices and inverseSlices for use in e.executeCall(). - if needsSlices { - // Round up the number of slices. - idx := e.Holder.Index(index) - if idx == nil { - return nil, ErrIndexNotFound - } - maxSlice := idx.MaxSlice() - maxInverseSlice := idx.MaxInverseSlice() + // If slices are specified, then use that value for slices or + // inverseSlices. If slices aren't specified, then include all of them. + if len(slices) > 0 { + // For inverse queries, the values of `slices` provided to the Execute() method + // on the remote node actually represents inverseSlices. + inverseSlices = slices + } else if needsSlices { + // Round up the number of slices. + idx := e.Holder.Index(index) + if idx == nil { + return nil, ErrIndexNotFound + } + maxSlice := idx.MaxSlice() + maxInverseSlice := idx.MaxInverseSlice() - // Generate a slices of all slices. - slices = make([]uint64, maxSlice+1) - for i := range slices { - slices[i] = uint64(i) - } + // Generate a slices of all slices. + slices = make([]uint64, maxSlice+1) + for i := range slices { + slices[i] = uint64(i) + } - // Generate a slices of all inverse slices. - inverseSlices = make([]uint64, maxInverseSlice+1) - for i := range inverseSlices { - inverseSlices[i] = uint64(i) - } + // Generate a slices of all inverse slices. + inverseSlices = make([]uint64, maxInverseSlice+1) + for i := range inverseSlices { + inverseSlices[i] = uint64(i) } } @@ -118,7 +120,6 @@ func (e *Executor) Execute(ctx context.Context, index string, q *pql.Query, slic // Execute each call serially. results := make([]interface{}, 0, len(q.Calls)) for _, call := range q.Calls { - if call.SupportsInverse() && needsSlices { // Fetch frame & row label based on argument. frame, _ := call.Args["frame"].(string) @@ -147,7 +148,6 @@ func (e *Executor) Execute(ctx context.Context, index string, q *pql.Query, slic // executeCall executes a call. func (e *Executor) executeCall(ctx context.Context, index string, c *pql.Call, slices []uint64, opt *ExecOptions) (interface{}, error) { - if err := e.validateCallArgs(c); err != nil { return nil, err } From 0d27139c04b4561ea9871d7ab4e38ade3ea4468e Mon Sep 17 00:00:00 2001 From: Travis Turner Date: Wed, 11 Apr 2018 12:51:26 -0500 Subject: [PATCH 12/12] test the inverseSlice fix --- server/server_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/server/server_test.go b/server/server_test.go index da62273a4..497ffd7da 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -26,6 +26,7 @@ import ( "strings" "testing" "testing/quick" + "time" "github.com/BurntSushi/toml" "github.com/pilosa/pilosa" @@ -234,6 +235,49 @@ func TestMain_SetColumnAttrs(t *testing.T) { } } +// Ensure inverse slices get handled correctly in a multi-node query. +func TestMain_InverseSlices(t *testing.T) { + mains := test.MustRunMainWithCluster(t, 2) + + m0 := mains[0] + m1 := mains[1] + + // Make sure to use node0 in the cluster. + var m *test.Main + if m0.Server.NodeID < m1.Server.NodeID { + m = m0 + } else { + m = m1 + } + + // Create frames. + client := m.Client() + if err := client.CreateIndex(context.Background(), "i", pilosa.IndexOptions{}); err != nil && err != pilosa.ErrIndexExists { + t.Fatal("create index:", err) + } + if err := client.CreateFrame(context.Background(), "i", "f", pilosa.FrameOptions{InverseEnabled: true}); err != nil { + t.Fatal("create frame:", err) + } + + // Write data on cluster. + if _, err := m.Query("i", "", fmt.Sprintf(` + SetBit(col=1, frame="f", row=1000) + SetBit(col=1, frame="f", row=2000) + SetBit(col=1, frame="f", row=%d) + `, 1*pilosa.SliceWidth)); err != nil { + t.Fatal("setting bits:", err) + } + + time.Sleep(1 * time.Second) + + // Query the cluster. + if res, err := m.Query("i", "", `Bitmap(col=1, frame="f")`); err != nil { + t.Fatal("another bitmap query:", err) + } else if res != fmt.Sprintf(`{"results":[{"attrs":{},"bits":[1000,2000,%d]}]}`, 1*pilosa.SliceWidth)+"\n" { + t.Fatalf("unexpected result: %s", res) + } +} + // Ensure program can set bits on one cluster and then restore to a second cluster. func TestMain_FrameRestore(t *testing.T) { mains1 := test.MustRunMainWithCluster(t, 2)