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 }