github.com/m3db/m3@v1.5.0/src/ctl/service/r2/service.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package r2
    22  
    23  import (
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"net/http"
    28  
    29  	"github.com/m3db/m3/src/ctl/auth"
    30  	mservice "github.com/m3db/m3/src/ctl/service"
    31  	"github.com/m3db/m3/src/ctl/service/r2/store"
    32  	"github.com/m3db/m3/src/x/clock"
    33  	"github.com/m3db/m3/src/x/instrument"
    34  
    35  	"github.com/gorilla/mux"
    36  	"github.com/uber-go/tally"
    37  	"go.uber.org/zap"
    38  )
    39  
    40  const (
    41  	namespacePath     = "/namespaces"
    42  	mappingRulePrefix = "mapping-rules"
    43  	rollupRulePrefix  = "rollup-rules"
    44  	namespaceIDVar    = "namespaceID"
    45  	ruleIDVar         = "ruleID"
    46  )
    47  
    48  var (
    49  	namespacePrefix     = fmt.Sprintf("%s/{%s}", namespacePath, namespaceIDVar)
    50  	validateRuleSetPath = fmt.Sprintf("%s/{%s}/ruleset/validate", namespacePath, namespaceIDVar)
    51  	updateRuleSetPath   = fmt.Sprintf("%s/{%s}/ruleset/update", namespacePath, namespaceIDVar)
    52  
    53  	mappingRuleRoot        = fmt.Sprintf("%s/%s", namespacePrefix, mappingRulePrefix)
    54  	mappingRuleWithIDPath  = fmt.Sprintf("%s/{%s}", mappingRuleRoot, ruleIDVar)
    55  	mappingRuleHistoryPath = fmt.Sprintf("%s/history", mappingRuleWithIDPath)
    56  
    57  	rollupRuleRoot        = fmt.Sprintf("%s/%s", namespacePrefix, rollupRulePrefix)
    58  	rollupRuleWithIDPath  = fmt.Sprintf("%s/{%s}", rollupRuleRoot, ruleIDVar)
    59  	rollupRuleHistoryPath = fmt.Sprintf("%s/history", rollupRuleWithIDPath)
    60  
    61  	errNilRequest = errors.New("Nil request")
    62  )
    63  
    64  type serviceMetrics struct {
    65  	fetchNamespaces         instrument.MethodMetrics
    66  	fetchNamespace          instrument.MethodMetrics
    67  	createNamespace         instrument.MethodMetrics
    68  	deleteNamespace         instrument.MethodMetrics
    69  	validateRuleSet         instrument.MethodMetrics
    70  	fetchMappingRule        instrument.MethodMetrics
    71  	createMappingRule       instrument.MethodMetrics
    72  	updateMappingRule       instrument.MethodMetrics
    73  	deleteMappingRule       instrument.MethodMetrics
    74  	fetchMappingRuleHistory instrument.MethodMetrics
    75  	fetchRollupRule         instrument.MethodMetrics
    76  	createRollupRule        instrument.MethodMetrics
    77  	updateRollupRule        instrument.MethodMetrics
    78  	deleteRollupRule        instrument.MethodMetrics
    79  	fetchRollupRuleHistory  instrument.MethodMetrics
    80  	updateRuleSet           instrument.MethodMetrics
    81  }
    82  
    83  func newServiceMetrics(scope tally.Scope, opts instrument.TimerOptions) serviceMetrics {
    84  	return serviceMetrics{
    85  		fetchNamespaces:         instrument.NewMethodMetrics(scope, "fetchNamespaces", opts),
    86  		fetchNamespace:          instrument.NewMethodMetrics(scope, "fetchNamespace", opts),
    87  		createNamespace:         instrument.NewMethodMetrics(scope, "createNamespace", opts),
    88  		deleteNamespace:         instrument.NewMethodMetrics(scope, "deleteNamespace", opts),
    89  		validateRuleSet:         instrument.NewMethodMetrics(scope, "validateRuleSet", opts),
    90  		fetchMappingRule:        instrument.NewMethodMetrics(scope, "fetchMappingRule", opts),
    91  		createMappingRule:       instrument.NewMethodMetrics(scope, "createMappingRule", opts),
    92  		updateMappingRule:       instrument.NewMethodMetrics(scope, "updateMappingRule", opts),
    93  		deleteMappingRule:       instrument.NewMethodMetrics(scope, "deleteMappingRule", opts),
    94  		fetchMappingRuleHistory: instrument.NewMethodMetrics(scope, "fetchMappingRuleHistory", opts),
    95  		fetchRollupRule:         instrument.NewMethodMetrics(scope, "fetchRollupRule", opts),
    96  		createRollupRule:        instrument.NewMethodMetrics(scope, "createRollupRule", opts),
    97  		updateRollupRule:        instrument.NewMethodMetrics(scope, "updateRollupRule", opts),
    98  		deleteRollupRule:        instrument.NewMethodMetrics(scope, "deleteRollupRule", opts),
    99  		fetchRollupRuleHistory:  instrument.NewMethodMetrics(scope, "fetchRollupRuleHistory", opts),
   100  		updateRuleSet:           instrument.NewMethodMetrics(scope, "updateRuleSet", opts),
   101  	}
   102  }
   103  
   104  var authorizationRegistry = map[route]auth.AuthorizationType{
   105  	// This validation route should only require read access.
   106  	{path: validateRuleSetPath, method: http.MethodPost}: auth.ReadOnlyAuthorization,
   107  }
   108  
   109  func defaultAuthorizationTypeForHTTPMethod(method string) (auth.AuthorizationType, error) {
   110  	switch method {
   111  	case http.MethodGet:
   112  		return auth.ReadOnlyAuthorization, nil
   113  	case http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch:
   114  		return auth.ReadWriteAuthorization, nil
   115  	default:
   116  		return auth.UnknownAuthorization, fmt.Errorf("unknown authorization type for method %s", method)
   117  	}
   118  }
   119  
   120  func registerRoute(router *mux.Router, path, method string, h r2Handler, hf r2HandlerFunc) error {
   121  	authType, exists := authorizationRegistry[route{path: path, method: method}]
   122  	if !exists {
   123  		var err error
   124  		if authType, err = defaultAuthorizationTypeForHTTPMethod(method); err != nil {
   125  			return fmt.Errorf("could not register route for method %s and path %s, error: %v", method, path, err)
   126  		}
   127  	}
   128  	fn := h.wrap(authType, hf)
   129  	router.Handle(path, fn).Methods(method)
   130  	return nil
   131  }
   132  
   133  // service handles all of the endpoints for r2.
   134  type service struct {
   135  	rootPrefix  string
   136  	store       store.Store
   137  	authService auth.HTTPAuthService
   138  	logger      *zap.Logger
   139  	nowFn       clock.NowFn
   140  	metrics     serviceMetrics
   141  }
   142  
   143  // NewService creates a new r2 service using a given store.
   144  func NewService(
   145  	rootPrefix string,
   146  	authService auth.HTTPAuthService,
   147  	store store.Store,
   148  	iOpts instrument.Options,
   149  	clockOpts clock.Options,
   150  ) mservice.Service {
   151  	return &service{
   152  		rootPrefix:  rootPrefix,
   153  		store:       store,
   154  		authService: authService,
   155  		logger:      iOpts.Logger(),
   156  		nowFn:       clockOpts.NowFn(),
   157  		metrics:     newServiceMetrics(iOpts.MetricsScope(), iOpts.TimerOptions()),
   158  	}
   159  }
   160  
   161  func (s *service) URLPrefix() string { return s.rootPrefix }
   162  
   163  func (s *service) RegisterHandlers(router *mux.Router) error {
   164  	routeWithHandlers := []struct {
   165  		route   route
   166  		handler r2HandlerFunc
   167  	}{
   168  		// Namespaces actions.
   169  		{route: route{path: namespacePath, method: http.MethodGet}, handler: s.fetchNamespaces},
   170  		{route: route{path: namespacePath, method: http.MethodPost}, handler: s.createNamespace},
   171  
   172  		// Ruleset actions.
   173  		{route: route{path: namespacePrefix, method: http.MethodGet}, handler: s.fetchNamespace},
   174  		{route: route{path: namespacePrefix, method: http.MethodDelete}, handler: s.deleteNamespace},
   175  		{route: route{path: validateRuleSetPath, method: http.MethodPost}, handler: s.validateRuleSet},
   176  		{route: route{path: updateRuleSetPath, method: http.MethodPost}, handler: s.updateRuleSet},
   177  
   178  		// Mapping Rule actions.
   179  		{route: route{path: mappingRuleRoot, method: http.MethodPost}, handler: s.createMappingRule},
   180  
   181  		{route: route{path: mappingRuleWithIDPath, method: http.MethodGet}, handler: s.fetchMappingRule},
   182  		{route: route{path: mappingRuleWithIDPath, method: http.MethodPut}, handler: s.updateMappingRule},
   183  		{route: route{path: mappingRuleWithIDPath, method: http.MethodDelete}, handler: s.deleteMappingRule},
   184  
   185  		// Mapping Rule history.
   186  		{route: route{path: mappingRuleHistoryPath, method: http.MethodGet}, handler: s.fetchMappingRuleHistory},
   187  
   188  		// Rollup Rule actions.
   189  		{route: route{path: rollupRuleRoot, method: http.MethodPost}, handler: s.createRollupRule},
   190  
   191  		{route: route{path: rollupRuleWithIDPath, method: http.MethodGet}, handler: s.fetchRollupRule},
   192  		{route: route{path: rollupRuleWithIDPath, method: http.MethodPut}, handler: s.updateRollupRule},
   193  		{route: route{path: rollupRuleWithIDPath, method: http.MethodDelete}, handler: s.deleteRollupRule},
   194  
   195  		// Rollup Rule history.
   196  		{route: route{path: rollupRuleHistoryPath, method: http.MethodGet}, handler: s.fetchRollupRuleHistory},
   197  	}
   198  
   199  	h := r2Handler{s.logger, s.authService}
   200  	for _, rh := range routeWithHandlers {
   201  		if err := registerRoute(router, rh.route.path, rh.route.method, h, rh.handler); err != nil {
   202  			return err
   203  		}
   204  	}
   205  	s.logger.Info("registered rules endpoints")
   206  	return nil
   207  }
   208  
   209  func (s *service) Close() { s.store.Close() }
   210  
   211  type routeFunc func(s *service, r *http.Request) (data interface{}, err error)
   212  
   213  func (s *service) handleRoute(rf routeFunc, r *http.Request, m instrument.MethodMetrics) (interface{}, error) {
   214  	if r == nil {
   215  		return nil, errNilRequest
   216  	}
   217  	start := s.nowFn()
   218  	data, err := rf(s, r)
   219  	dur := s.nowFn().Sub(start)
   220  	m.ReportSuccessOrError(err, dur)
   221  	s.logRequest(r, err)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	return data, nil
   226  }
   227  
   228  func (s *service) logRequest(r *http.Request, err error) {
   229  	logger := s.logger.With(
   230  		zap.String("httpMethod", r.Method),
   231  		zap.String("routePath", r.RequestURI),
   232  	)
   233  	if err != nil {
   234  		logger.With(zap.Error(err)).Error("request error")
   235  		return
   236  	}
   237  	logger.Info("request success")
   238  }
   239  
   240  func (s *service) sendResponse(w http.ResponseWriter, statusCode int, data interface{}) error {
   241  	if j, err := json.Marshal(data); err == nil {
   242  		return sendResponse(w, j, statusCode)
   243  	}
   244  	return writeAPIResponse(w, http.StatusInternalServerError, "could not create response object")
   245  }
   246  
   247  func (s *service) fetchNamespaces(w http.ResponseWriter, r *http.Request) error {
   248  	data, err := s.handleRoute(fetchNamespaces, r, s.metrics.fetchNamespaces)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	return s.sendResponse(w, http.StatusOK, data)
   253  }
   254  
   255  func (s *service) fetchNamespace(w http.ResponseWriter, r *http.Request) error {
   256  	data, err := s.handleRoute(fetchNamespace, r, s.metrics.fetchNamespace)
   257  	if err != nil {
   258  		return err
   259  	}
   260  	return s.sendResponse(w, http.StatusOK, data)
   261  }
   262  
   263  func (s *service) createNamespace(w http.ResponseWriter, r *http.Request) error {
   264  	data, err := s.handleRoute(createNamespace, r, s.metrics.createNamespace)
   265  	if err != nil {
   266  		return err
   267  	}
   268  	return s.sendResponse(w, http.StatusCreated, data)
   269  }
   270  
   271  func (s *service) validateRuleSet(w http.ResponseWriter, r *http.Request) error {
   272  	data, err := s.handleRoute(validateRuleSet, r, s.metrics.validateRuleSet)
   273  	if err != nil {
   274  		return err
   275  	}
   276  	return writeAPIResponse(w, http.StatusOK, data.(string))
   277  }
   278  
   279  func (s *service) updateRuleSet(w http.ResponseWriter, r *http.Request) error {
   280  	data, err := s.handleRoute(updateRuleSet, r, s.metrics.updateRuleSet)
   281  	if err != nil {
   282  		return err
   283  	}
   284  	return s.sendResponse(w, http.StatusOK, data)
   285  }
   286  
   287  func (s *service) deleteNamespace(w http.ResponseWriter, r *http.Request) error {
   288  	data, err := s.handleRoute(deleteNamespace, r, s.metrics.deleteNamespace)
   289  	if err != nil {
   290  		return err
   291  	}
   292  	return writeAPIResponse(w, http.StatusOK, data.(string))
   293  }
   294  
   295  func (s *service) fetchMappingRule(w http.ResponseWriter, r *http.Request) error {
   296  	data, err := s.handleRoute(fetchMappingRule, r, s.metrics.fetchMappingRule)
   297  	if err != nil {
   298  		return err
   299  	}
   300  	return s.sendResponse(w, http.StatusOK, data)
   301  }
   302  
   303  func (s *service) createMappingRule(w http.ResponseWriter, r *http.Request) error {
   304  	data, err := s.handleRoute(createMappingRule, r, s.metrics.createMappingRule)
   305  	if err != nil {
   306  		return err
   307  	}
   308  	return s.sendResponse(w, http.StatusCreated, data)
   309  }
   310  
   311  func (s *service) updateMappingRule(w http.ResponseWriter, r *http.Request) error {
   312  	data, err := s.handleRoute(updateMappingRule, r, s.metrics.updateMappingRule)
   313  	if err != nil {
   314  		return err
   315  	}
   316  	return s.sendResponse(w, http.StatusOK, data)
   317  }
   318  
   319  func (s *service) deleteMappingRule(w http.ResponseWriter, r *http.Request) error {
   320  	data, err := s.handleRoute(deleteMappingRule, r, s.metrics.deleteMappingRule)
   321  	if err != nil {
   322  		return err
   323  	}
   324  	return writeAPIResponse(w, http.StatusOK, data.(string))
   325  }
   326  
   327  func (s *service) fetchMappingRuleHistory(w http.ResponseWriter, r *http.Request) error {
   328  	data, err := s.handleRoute(fetchMappingRuleHistory, r, s.metrics.fetchMappingRuleHistory)
   329  	if err != nil {
   330  		return err
   331  	}
   332  	return s.sendResponse(w, http.StatusOK, data)
   333  }
   334  
   335  func (s *service) fetchRollupRule(w http.ResponseWriter, r *http.Request) error {
   336  	data, err := s.handleRoute(fetchRollupRule, r, s.metrics.fetchRollupRule)
   337  	if err != nil {
   338  		return err
   339  	}
   340  	return s.sendResponse(w, http.StatusOK, data)
   341  }
   342  
   343  func (s *service) createRollupRule(w http.ResponseWriter, r *http.Request) error {
   344  	data, err := s.handleRoute(createRollupRule, r, s.metrics.createRollupRule)
   345  	if err != nil {
   346  		return err
   347  	}
   348  	return s.sendResponse(w, http.StatusCreated, data)
   349  }
   350  
   351  func (s *service) updateRollupRule(w http.ResponseWriter, r *http.Request) error {
   352  	data, err := s.handleRoute(updateRollupRule, r, s.metrics.updateRollupRule)
   353  	if err != nil {
   354  		return err
   355  	}
   356  	return s.sendResponse(w, http.StatusOK, data)
   357  }
   358  
   359  func (s *service) deleteRollupRule(w http.ResponseWriter, r *http.Request) error {
   360  	data, err := s.handleRoute(deleteRollupRule, r, s.metrics.deleteRollupRule)
   361  	if err != nil {
   362  		return err
   363  	}
   364  	return writeAPIResponse(w, http.StatusOK, data.(string))
   365  }
   366  
   367  func (s *service) fetchRollupRuleHistory(w http.ResponseWriter, r *http.Request) error {
   368  	data, err := s.handleRoute(fetchRollupRuleHistory, r, s.metrics.fetchRollupRuleHistory)
   369  	if err != nil {
   370  		return err
   371  	}
   372  	return s.sendResponse(w, http.StatusOK, data)
   373  }
   374  
   375  type route struct {
   376  	path   string
   377  	method string
   378  }
   379  
   380  func (s *service) newUpdateOptions(r *http.Request) (store.UpdateOptions, error) {
   381  	uOpts := store.NewUpdateOptions()
   382  	author, err := s.authService.GetUser(r.Context())
   383  	if err != nil {
   384  		return uOpts, nil
   385  	}
   386  	return uOpts.SetAuthor(author), nil
   387  }