From 54680652137aee9b16cb4999b15d66d6c27020fa Mon Sep 17 00:00:00 2001 From: Travis Turner Date: Tue, 17 Apr 2018 13:01:36 -0500 Subject: [PATCH] improve apiFunc error handling. change slice to map in function validation. --- Makefile | 6 +- api.go | 173 +++++++++++++++++++++++++--------------------- apifunc_string.go | 16 +++++ pilosa.go | 6 ++ 4 files changed, 120 insertions(+), 81 deletions(-) create mode 100644 apifunc_string.go diff --git a/Makefile b/Makefile index 9e2c6fa3f..cab932fba 100644 --- a/Makefile +++ b/Makefile @@ -80,8 +80,12 @@ generate-protoc: require-protoc require-protoc-gen-gofast generate-statik: require-statik go generate github.com/pilosa/pilosa/statik +# `go generate` stringers +generate-stringer: + go generate github.com/pilosa/pilosa + # `go generate` all needed packages -generate: generate-protoc generate-statik +generate: generate-protoc generate-statik generate-stringer # Create Docker image from Dockerfile docker: diff --git a/api.go b/api.go index 970766842..6852a1f87 100644 --- a/api.go +++ b/api.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:generate stringer -type=apiFunc + package pilosa import ( @@ -61,26 +63,35 @@ func NewAPI() *API { // functionStates specifies the api functions that are valid for each // cluster state. -var functionStates = map[string][]int{ +var functionStates = map[string]map[apiFunc]struct{}{ ClusterStateStarting: functionCommon, - ClusterStateNormal: append(functionCommon, functionNormal...), - ClusterStateResizing: append(functionCommon, functionResizing...), + ClusterStateNormal: appendMap(functionCommon, functionNormal), + ClusterStateResizing: appendMap(functionCommon, functionResizing), } -func (api *API) validate(f int) error { - state := api.Cluster.State() - for _, fnc := range functionStates[state] { - if f == fnc { - return nil - } +func appendMap(a, b map[apiFunc]struct{}) map[apiFunc]struct{} { + r := make(map[apiFunc]struct{}) + for k, v := range a { + r[k] = v } - return fmt.Errorf("api function not allowed in state %s", state) + for k, v := range b { + r[k] = v + } + return r +} + +func (api *API) validate(f apiFunc) error { + state := api.Cluster.State() + if _, ok := functionStates[state][f]; ok { + return nil + } + return ApiFunctionNotAllowedError{errors.Errorf("api function %s not allowed in state %s", f, state)} } // Query parses a PQL query out of the request and executes it. func (api *API) Query(ctx context.Context, req *QueryRequest) (QueryResponse, error) { if err := api.validate(apiQuery); err != nil { - return QueryResponse{}, errors.Wrap(err, "validate api function: query") + return QueryResponse{}, errors.Wrap(err, "validate api function") } resp := QueryResponse{} @@ -148,7 +159,7 @@ func (api *API) readColumnAttrSets(index *Index, ids []uint64) ([]*ColumnAttrSet // CreateIndex makes a new Pilosa index. func (api *API) CreateIndex(ctx context.Context, indexName string, options IndexOptions) (*Index, error) { if err := api.validate(apiCreateIndex); err != nil { - return nil, errors.Wrap(err, "validate api function: create index") + return nil, errors.Wrap(err, "validate api function") } // Create index. @@ -173,7 +184,7 @@ func (api *API) CreateIndex(ctx context.Context, indexName string, options Index // Index retrieves the named index. func (api *API) Index(ctx context.Context, indexName string) (*Index, error) { if err := api.validate(apiIndex); err != nil { - return nil, errors.Wrap(err, "validate api function: index") + return nil, errors.Wrap(err, "validate api function") } index := api.Holder.Index(indexName) @@ -187,7 +198,7 @@ func (api *API) Index(ctx context.Context, indexName string) (*Index, error) { // nothing and returns no error. func (api *API) DeleteIndex(ctx context.Context, indexName string) error { if err := api.validate(apiDeleteIndex); err != nil { - return errors.Wrap(err, "validate api function: delete index") + return errors.Wrap(err, "validate api function") } // Delete index from the holder. @@ -211,7 +222,7 @@ func (api *API) DeleteIndex(ctx context.Context, indexName string) error { // CreateFrame makes the named frame in the named index with the given options. func (api *API) CreateFrame(ctx context.Context, indexName string, frameName string, options FrameOptions) (*Frame, error) { if err := api.validate(apiCreateFrame); err != nil { - return nil, errors.Wrap(err, "validate api function: create frame") + return nil, errors.Wrap(err, "validate api function") } // Find index. @@ -246,7 +257,7 @@ func (api *API) CreateFrame(ctx context.Context, indexName string, frameName str // action is taken. func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName string) error { if err := api.validate(apiDeleteFrame); err != nil { - return errors.Wrap(err, "validate api function: delete frame") + return errors.Wrap(err, "validate api function") } // Find index. @@ -278,7 +289,7 @@ func (api *API) DeleteFrame(ctx context.Context, indexName string, frameName str // CSV of the form , func (api *API) ExportCSV(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, w io.Writer) error { if err := api.validate(apiExportCSV); err != nil { - return errors.Wrap(err, "validate api function: export csv") + return errors.Wrap(err, "validate api function") } // Validate that this handler owns the slice. @@ -315,7 +326,7 @@ func (api *API) ExportCSV(ctx context.Context, indexName string, frameName strin // SliceNodes returns the node and all replicas which should contain a slice's data. func (api *API) SliceNodes(ctx context.Context, indexName string, slice uint64) ([]*Node, error) { if err := api.validate(apiSliceNodes); err != nil { - return nil, errors.Wrap(err, "validate api function: slice nodes") + return nil, errors.Wrap(err, "validate api function") } return api.Cluster.SliceNodes(indexName, slice), nil @@ -326,7 +337,7 @@ func (api *API) SliceNodes(ctx context.Context, indexName string, slice uint64) // the UnmarshalFragment API call. func (api *API) MarshalFragment(ctx context.Context, indexName string, frameName string, viewName string, slice uint64) (io.WriterTo, error) { if err := api.validate(apiMarshalFragment); err != nil { - return nil, errors.Wrap(err, "validate api function: marshal fragment") + return nil, errors.Wrap(err, "validate api function") } // Retrieve fragment from holder. @@ -342,7 +353,7 @@ func (api *API) MarshalFragment(ctx context.Context, indexName string, frameName // fragment's data. func (api *API) UnmarshalFragment(ctx context.Context, indexName string, frameName string, viewName string, slice uint64, reader io.ReadCloser) error { if err := api.validate(apiUnmarshalFragment); err != nil { - return errors.Wrap(err, "validate api function: unmarshal fragment") + return errors.Wrap(err, "validate api function") } // Retrieve frame. @@ -375,7 +386,7 @@ func (api *API) UnmarshalFragment(ctx context.Context, indexName string, frameNa // ids from a "block" which is a subdivision of a fragment. func (api *API) FragmentBlockData(ctx context.Context, body io.Reader) ([]byte, error) { if err := api.validate(apiFragmentBlockData); err != nil { - return nil, errors.Wrap(err, "validate api function: fragment block data") + return nil, errors.Wrap(err, "validate api function") } reqBytes, err := ioutil.ReadAll(body) @@ -408,7 +419,7 @@ func (api *API) FragmentBlockData(ctx context.Context, body io.Reader) ([]byte, // FragmentBlocks returns the checksums and block ids for all blocks in the specified fragment. func (api *API) FragmentBlocks(ctx context.Context, indexName string, frameName string, viewName string, slice uint64) ([]FragmentBlock, error) { if err := api.validate(apiFragmentBlocks); err != nil { - return nil, errors.Wrap(err, "validate api function: fragment blocks") + return nil, errors.Wrap(err, "validate api function") } // Retrieve fragment from holder. @@ -426,7 +437,7 @@ func (api *API) FragmentBlocks(ctx context.Context, indexName string, frameName // from replicas in the cluster and restores that data to it. func (api *API) RestoreFrame(ctx context.Context, indexName string, frameName string, host *URI) error { if err := api.validate(apiRestoreFrame); err != nil { - return errors.Wrap(err, "validate api function: restore frame") + return errors.Wrap(err, "validate api function") } // Create a client for the remote cluster. @@ -504,7 +515,7 @@ func (api *API) Hosts(ctx context.Context) []*Node { // CreateInputDefinition is deprecated and will be removed. Do not use it. func (api *API) CreateInputDefinition(ctx context.Context, indexName string, inputDefName string, inputDef InputDefinitionInfo) error { if err := api.validate(apiCreateInputDefinition); err != nil { - return errors.Wrap(err, "validate api function: create input definition") + return errors.Wrap(err, "validate api function") } api.Logger.Printf(`CreateInputDefinition is deprecated and will be removed. @@ -542,7 +553,7 @@ Please open an issue if you need to continue using it.`) // InputDefinition is deprecated and will be removed. func (api *API) InputDefinition(ctx context.Context, indexName string, inputDefName string) (*InputDefinition, error) { if err := api.validate(apiInputDefinition); err != nil { - return nil, errors.Wrap(err, "validate api function: input definition") + return nil, errors.Wrap(err, "validate api function") } api.Logger.Printf(`InputDefinition is deprecated and will be removed.`) @@ -562,7 +573,7 @@ func (api *API) InputDefinition(ctx context.Context, indexName string, inputDefN // DeleteInputDefinition is deprecated and will be removed. func (api *API) DeleteInputDefinition(ctx context.Context, indexName string, inputDefName string) error { if err := api.validate(apiDeleteInputDefinition); err != nil { - return errors.Wrap(err, "validate api function: delete input definition") + return errors.Wrap(err, "validate api function") } api.Logger.Printf("DeleteInputDefinition is deprecated and will be removed.") @@ -591,7 +602,7 @@ func (api *API) DeleteInputDefinition(ctx context.Context, indexName string, inp // WriteInput is deprecated and will be removed. func (api *API) WriteInput(ctx context.Context, indexName string, inputDefName string, reqs []interface{}) error { if err := api.validate(apiWriteInput); err != nil { - return errors.Wrap(err, "validate api function: write input") + return errors.Wrap(err, "validate api function") } api.Logger.Printf("WriteInput is deprecated and will be removed.") @@ -619,7 +630,7 @@ func (api *API) WriteInput(ctx context.Context, indexName string, inputDefName s // RecalculateCaches forces all TopN caches to be updated. Used mainly for integration tests. func (api *API) RecalculateCaches(ctx context.Context) error { if err := api.validate(apiRecalculateCaches); err != nil { - return errors.Wrap(err, "validate api function: recalculate caches") + return errors.Wrap(err, "validate api function") } err := api.Broadcaster.SendSync(&internal.RecalculateCaches{}) @@ -634,7 +645,7 @@ func (api *API) RecalculateCaches(ctx context.Context) error { // the body and forwards it to the BroadcastHandler. func (api *API) ClusterMessage(ctx context.Context, reqBody io.Reader) error { if err := api.validate(apiClusterMessage); err != nil { - return errors.Wrap(err, "validate api function: cluster message") + return errors.Wrap(err, "validate api function") } // Read entire body. @@ -670,7 +681,7 @@ func (api *API) Schema(ctx context.Context) []*IndexInfo { // CreateField creates a new BSI field in the given index and frame. func (api *API) CreateField(ctx context.Context, indexName string, frameName string, field *Field) error { if err := api.validate(apiCreateField); err != nil { - return errors.Wrap(err, "validate api function: create field") + return errors.Wrap(err, "validate api function") } // Retrieve frame by name. @@ -700,7 +711,7 @@ func (api *API) CreateField(ctx context.Context, indexName string, frameName str // DeleteField deletes the given field. func (api *API) DeleteField(ctx context.Context, indexName string, frameName string, fieldName string) error { if err := api.validate(apiDeleteField); err != nil { - return errors.Wrap(err, "validate api function: delete field") + return errors.Wrap(err, "validate api function") } // Retrieve frame by name. @@ -730,7 +741,7 @@ func (api *API) DeleteField(ctx context.Context, indexName string, frameName str // Fields returns the fields in the given frame. func (api *API) Fields(ctx context.Context, indexName string, frameName string) ([]*Field, error) { if err := api.validate(apiFields); err != nil { - return nil, errors.Wrap(err, "validate api function: fields") + return nil, errors.Wrap(err, "validate api function") } index := api.Holder.index(indexName) @@ -749,7 +760,7 @@ func (api *API) Fields(ctx context.Context, indexName string, frameName string) // Views returns the views in the given frame. func (api *API) Views(ctx context.Context, indexName string, frameName string) ([]*View, error) { if err := api.validate(apiViews); err != nil { - return nil, errors.Wrap(err, "validate api function: views") + return nil, errors.Wrap(err, "validate api function") } // Retrieve views. @@ -766,7 +777,7 @@ func (api *API) Views(ctx context.Context, indexName string, frameName string) ( // DeleteView removes the given view. func (api *API) DeleteView(ctx context.Context, indexName string, frameName string, viewName string) error { if err := api.validate(apiDeleteView); err != nil { - return errors.Wrap(err, "validate api function: delete view") + return errors.Wrap(err, "validate api function") } // Retrieve frame. @@ -800,7 +811,7 @@ func (api *API) DeleteView(ctx context.Context, indexName string, frameName stri // IndexAttrDiff func (api *API) IndexAttrDiff(ctx context.Context, indexName string, blocks []AttrBlock) (map[uint64]map[string]interface{}, error) { if err := api.validate(apiIndexAttrDiff); err != nil { - return nil, errors.Wrap(err, "validate api function: index attr diff") + return nil, errors.Wrap(err, "validate api function") } // Retrieve index from holder. @@ -834,7 +845,7 @@ func (api *API) IndexAttrDiff(ctx context.Context, indexName string, blocks []At func (api *API) FrameAttrDiff(ctx context.Context, indexName string, frameName string, blocks []AttrBlock) (map[uint64]map[string]interface{}, error) { if err := api.validate(apiFrameAttrDiff); err != nil { - return nil, errors.Wrap(err, "validate api function: frame attr diff") + return nil, errors.Wrap(err, "validate api function") } // Retrieve index from holder. @@ -869,7 +880,7 @@ func (api *API) FrameAttrDiff(ctx context.Context, indexName string, frameName s // Import bulk imports data into a particular index,frame,slice. func (api *API) Import(ctx context.Context, req internal.ImportRequest) error { if err := api.validate(apiImport); err != nil { - return errors.Wrap(err, "validate api function: import") + return errors.Wrap(err, "validate api function") } _, frame, err := api.indexFrame(req.Index, req.Frame, req.Slice) @@ -898,7 +909,7 @@ func (api *API) Import(ctx context.Context, req internal.ImportRequest) error { // ImportValue bulk imports values into a particular field. func (api *API) ImportValue(ctx context.Context, req internal.ImportValueRequest) error { if err := api.validate(apiImportValue); err != nil { - return errors.Wrap(err, "validate api function: import value") + return errors.Wrap(err, "validate api function") } _, frame, err := api.indexFrame(req.Index, req.Frame, req.Slice) @@ -917,7 +928,7 @@ func (api *API) ImportValue(ctx context.Context, req internal.ImportValueRequest // ModifyIndexTimeQuantum changes the default time quantum on the given index. func (api *API) ModifyIndexTimeQuantum(ctx context.Context, indexName string, timeQuantum TimeQuantum) error { if err := api.validate(apiModifyIndexTimeQuantum); err != nil { - return errors.Wrap(err, "validate api function: modify index time quantum") + return errors.Wrap(err, "validate api function") } // Retrieve index by name. @@ -934,7 +945,7 @@ func (api *API) ModifyIndexTimeQuantum(ctx context.Context, indexName string, ti // what happens if there is already data in the frame? func (api *API) ModifyFrameTimeQuantum(ctx context.Context, indexName string, frameName string, timeQuantum TimeQuantum) error { if err := api.validate(apiModifyFrameTimeQuantum); err != nil { - return errors.Wrap(err, "validate api function: modify frame time quantum") + return errors.Wrap(err, "validate api function") } // Retrieve index by name. @@ -1072,7 +1083,7 @@ func (api *API) inputJSONDataParser(req map[string]interface{}, index *Index, na // SetCoordinator makes a new Node the cluster coordinator. func (api *API) SetCoordinator(ctx context.Context, id string) (oldNode, newNode *Node, err error) { if err := api.validate(apiSetCoordinator); err != nil { - return nil, nil, errors.Wrap(err, "validate api function: set coordinator") + return nil, nil, errors.Wrap(err, "validate api function") } oldNode = api.Cluster.nodeByID(api.Cluster.Coordinator) @@ -1102,7 +1113,7 @@ func (api *API) SetCoordinator(ctx context.Context, id string) (oldNode, newNode // removing the given node. func (api *API) RemoveNode(id string) (*Node, error) { if err := api.validate(apiRemoveNode); err != nil { - return nil, errors.Wrap(err, "validate api function: remove node") + return nil, errors.Wrap(err, "validate api function") } removeNode := api.Cluster.nodeByID(id) @@ -1121,7 +1132,7 @@ func (api *API) RemoveNode(id string) (*Node, error) { // ResizeAbort stops the current resize job. func (api *API) ResizeAbort() error { if err := api.validate(apiResizeAbort); err != nil { - return errors.Wrap(err, "validate api function: resize abort") + return errors.Wrap(err, "validate api function") } if !api.Cluster.IsCoordinator() { @@ -1143,9 +1154,11 @@ func (api *API) Version() string { return strings.TrimPrefix(Version, "v") } +type apiFunc int + // API validation constants. const ( - apiClusterMessage int = iota + apiClusterMessage apiFunc = iota apiCreateField apiCreateFrame apiCreateIndex @@ -1189,44 +1202,44 @@ const ( apiWriteInput ) -var functionCommon = []int{ - apiClusterMessage, - apiMarshalFragment, - apiSetCoordinator, +var functionCommon = map[apiFunc]struct{}{ + apiClusterMessage: struct{}{}, + apiMarshalFragment: struct{}{}, + apiSetCoordinator: struct{}{}, } -var functionResizing = []int{ - apiResizeAbort, +var functionResizing = map[apiFunc]struct{}{ + apiResizeAbort: struct{}{}, } -var functionNormal = []int{ - apiCreateField, - apiCreateFrame, - apiCreateIndex, - apiCreateInputDefinition, - apiDeleteField, - apiDeleteFrame, - apiDeleteIndex, - apiDeleteInputDefinition, - apiDeleteView, - apiExportCSV, - apiFields, - apiFragmentBlockData, - apiFragmentBlocks, - apiFrameAttrDiff, - apiImport, - apiImportValue, - apiIndex, - apiIndexAttrDiff, - apiInputDefinition, - apiModifyFrameTimeQuantum, - apiModifyIndexTimeQuantum, - apiQuery, - apiRecalculateCaches, - apiRemoveNode, - apiRestoreFrame, - apiSliceNodes, - apiUnmarshalFragment, - apiViews, - apiWriteInput, +var functionNormal = map[apiFunc]struct{}{ + apiCreateField: struct{}{}, + apiCreateFrame: struct{}{}, + apiCreateIndex: struct{}{}, + apiCreateInputDefinition: struct{}{}, + apiDeleteField: struct{}{}, + apiDeleteFrame: struct{}{}, + apiDeleteIndex: struct{}{}, + apiDeleteInputDefinition: struct{}{}, + apiDeleteView: struct{}{}, + apiExportCSV: struct{}{}, + apiFields: struct{}{}, + apiFragmentBlockData: struct{}{}, + apiFragmentBlocks: struct{}{}, + apiFrameAttrDiff: struct{}{}, + apiImport: struct{}{}, + apiImportValue: struct{}{}, + apiIndex: struct{}{}, + apiIndexAttrDiff: struct{}{}, + apiInputDefinition: struct{}{}, + apiModifyFrameTimeQuantum: struct{}{}, + apiModifyIndexTimeQuantum: struct{}{}, + apiQuery: struct{}{}, + apiRecalculateCaches: struct{}{}, + apiRemoveNode: struct{}{}, + apiRestoreFrame: struct{}{}, + apiSliceNodes: struct{}{}, + apiUnmarshalFragment: struct{}{}, + apiViews: struct{}{}, + apiWriteInput: struct{}{}, } diff --git a/apifunc_string.go b/apifunc_string.go new file mode 100644 index 000000000..8fb9b16e0 --- /dev/null +++ b/apifunc_string.go @@ -0,0 +1,16 @@ +// Code generated by "stringer -type=apiFunc"; DO NOT EDIT. + +package pilosa + +import "fmt" + +const _apiFunc_name = "apiClusterMessageapiCreateFieldapiCreateFrameapiCreateIndexapiCreateInputDefinitionapiDeleteFieldapiDeleteFrameapiDeleteIndexapiDeleteInputDefinitionapiDeleteViewapiExportCSVapiFieldsapiFragmentBlockDataapiFragmentBlocksapiFrameAttrDiffapiImportapiImportValueapiIndexapiIndexAttrDiffapiInputDefinitionapiMarshalFragmentapiModifyFrameTimeQuantumapiModifyIndexTimeQuantumapiQueryapiRecalculateCachesapiRemoveNodeapiResizeAbortapiRestoreFrameapiSetCoordinatorapiSliceNodesapiUnmarshalFragmentapiViewsapiWriteInput" + +var _apiFunc_index = [...]uint16{0, 17, 31, 45, 59, 83, 97, 111, 125, 149, 162, 174, 183, 203, 220, 236, 245, 259, 267, 283, 301, 319, 344, 369, 377, 397, 410, 424, 439, 456, 469, 489, 497, 510} + +func (i apiFunc) String() string { + if i < 0 || i >= apiFunc(len(_apiFunc_index)-1) { + return fmt.Sprintf("apiFunc(%d)", i) + } + return _apiFunc_name[_apiFunc_index[i]:_apiFunc_index[i+1]] +} diff --git a/pilosa.go b/pilosa.go index ffb836ae8..c3a6919db 100644 --- a/pilosa.go +++ b/pilosa.go @@ -82,6 +82,12 @@ var ( ErrResizeNotRunning = errors.New("no resize job currently running") ) +// InvalidApiFunctionError wraps an error value indicating that a particular +// API function is not allowed in the current cluster state. +type ApiFunctionNotAllowedError struct { + error +} + // BadRequestError wraps an error value to signify that a request could not be // read, decoded, or parsed such that in an HTTP scenario, http.StatusBadRequest // would be returned.