1package api
2
3import (
4	"context"
5	"flag"
6	"net/http"
7	"path"
8	"strings"
9	"time"
10
11	"github.com/NYTimes/gziphandler"
12	"github.com/felixge/fgprof"
13	"github.com/go-kit/log"
14	"github.com/go-kit/log/level"
15	"github.com/prometheus/client_golang/prometheus"
16	"github.com/prometheus/prometheus/storage"
17	"github.com/weaveworks/common/middleware"
18	"github.com/weaveworks/common/server"
19
20	"github.com/cortexproject/cortex/pkg/alertmanager"
21	"github.com/cortexproject/cortex/pkg/alertmanager/alertmanagerpb"
22	"github.com/cortexproject/cortex/pkg/chunk/purger"
23	"github.com/cortexproject/cortex/pkg/compactor"
24	"github.com/cortexproject/cortex/pkg/cortexpb"
25	"github.com/cortexproject/cortex/pkg/distributor"
26	"github.com/cortexproject/cortex/pkg/distributor/distributorpb"
27	frontendv1 "github.com/cortexproject/cortex/pkg/frontend/v1"
28	"github.com/cortexproject/cortex/pkg/frontend/v1/frontendv1pb"
29	frontendv2 "github.com/cortexproject/cortex/pkg/frontend/v2"
30	"github.com/cortexproject/cortex/pkg/frontend/v2/frontendv2pb"
31	"github.com/cortexproject/cortex/pkg/ingester/client"
32	"github.com/cortexproject/cortex/pkg/querier"
33	"github.com/cortexproject/cortex/pkg/ring"
34	"github.com/cortexproject/cortex/pkg/ruler"
35	"github.com/cortexproject/cortex/pkg/scheduler"
36	"github.com/cortexproject/cortex/pkg/scheduler/schedulerpb"
37	"github.com/cortexproject/cortex/pkg/storegateway"
38	"github.com/cortexproject/cortex/pkg/storegateway/storegatewaypb"
39	"github.com/cortexproject/cortex/pkg/util/push"
40)
41
42// DistributorPushWrapper wraps around a push. It is similar to middleware.Interface.
43type DistributorPushWrapper func(next push.Func) push.Func
44type ConfigHandler func(actualCfg interface{}, defaultCfg interface{}) http.HandlerFunc
45
46type Config struct {
47	ResponseCompression bool `yaml:"response_compression_enabled"`
48
49	AlertmanagerHTTPPrefix string `yaml:"alertmanager_http_prefix"`
50	PrometheusHTTPPrefix   string `yaml:"prometheus_http_prefix"`
51
52	// The following configs are injected by the upstream caller.
53	ServerPrefix       string               `yaml:"-"`
54	LegacyHTTPPrefix   string               `yaml:"-"`
55	HTTPAuthMiddleware middleware.Interface `yaml:"-"`
56
57	// This allows downstream projects to wrap the distributor push function
58	// and access the deserialized write requests before/after they are pushed.
59	DistributorPushWrapper DistributorPushWrapper `yaml:"-"`
60
61	// The CustomConfigHandler allows for providing a different handler for the
62	// `/config` endpoint. If this field is set _before_ the API module is
63	// initialized, the custom config handler will be used instead of
64	// DefaultConfigHandler.
65	CustomConfigHandler ConfigHandler `yaml:"-"`
66}
67
68// RegisterFlags adds the flags required to config this to the given FlagSet.
69func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
70	f.BoolVar(&cfg.ResponseCompression, "api.response-compression-enabled", false, "Use GZIP compression for API responses. Some endpoints serve large YAML or JSON blobs which can benefit from compression.")
71	cfg.RegisterFlagsWithPrefix("", f)
72}
73
74// RegisterFlagsWithPrefix adds the flags required to config this to the given FlagSet with the set prefix.
75func (cfg *Config) RegisterFlagsWithPrefix(prefix string, f *flag.FlagSet) {
76	f.StringVar(&cfg.AlertmanagerHTTPPrefix, prefix+"http.alertmanager-http-prefix", "/alertmanager", "HTTP URL path under which the Alertmanager ui and api will be served.")
77	f.StringVar(&cfg.PrometheusHTTPPrefix, prefix+"http.prometheus-http-prefix", "/prometheus", "HTTP URL path under which the Prometheus api will be served.")
78}
79
80// Push either wraps the distributor push function as configured or returns the distributor push directly.
81func (cfg *Config) wrapDistributorPush(d *distributor.Distributor) push.Func {
82	if cfg.DistributorPushWrapper != nil {
83		return cfg.DistributorPushWrapper(d.Push)
84	}
85
86	return d.Push
87}
88
89type API struct {
90	AuthMiddleware middleware.Interface
91
92	cfg       Config
93	server    *server.Server
94	logger    log.Logger
95	sourceIPs *middleware.SourceIPExtractor
96	indexPage *IndexPageContent
97}
98
99func New(cfg Config, serverCfg server.Config, s *server.Server, logger log.Logger) (*API, error) {
100	// Ensure the encoded path is used. Required for the rules API
101	s.HTTP.UseEncodedPath()
102
103	var sourceIPs *middleware.SourceIPExtractor
104	if serverCfg.LogSourceIPs {
105		var err error
106		sourceIPs, err = middleware.NewSourceIPs(serverCfg.LogSourceIPsHeader, serverCfg.LogSourceIPsRegex)
107		if err != nil {
108			// This should have already been caught in the Server creation
109			return nil, err
110		}
111	}
112
113	api := &API{
114		cfg:            cfg,
115		AuthMiddleware: cfg.HTTPAuthMiddleware,
116		server:         s,
117		logger:         logger,
118		sourceIPs:      sourceIPs,
119		indexPage:      newIndexPageContent(),
120	}
121
122	// If no authentication middleware is present in the config, use the default authentication middleware.
123	if cfg.HTTPAuthMiddleware == nil {
124		api.AuthMiddleware = middleware.AuthenticateUser
125	}
126
127	return api, nil
128}
129
130// RegisterRoute registers a single route enforcing HTTP methods. A single
131// route is expected to be specific about which HTTP methods are supported.
132func (a *API) RegisterRoute(path string, handler http.Handler, auth bool, method string, methods ...string) {
133	methods = append([]string{method}, methods...)
134
135	level.Debug(a.logger).Log("msg", "api: registering route", "methods", strings.Join(methods, ","), "path", path, "auth", auth)
136
137	if auth {
138		handler = a.AuthMiddleware.Wrap(handler)
139	}
140
141	if a.cfg.ResponseCompression {
142		handler = gziphandler.GzipHandler(handler)
143	}
144
145	if len(methods) == 0 {
146		a.server.HTTP.Path(path).Handler(handler)
147		return
148	}
149	a.server.HTTP.Path(path).Methods(methods...).Handler(handler)
150}
151
152func (a *API) RegisterRoutesWithPrefix(prefix string, handler http.Handler, auth bool, methods ...string) {
153	level.Debug(a.logger).Log("msg", "api: registering route", "methods", strings.Join(methods, ","), "prefix", prefix, "auth", auth)
154	if auth {
155		handler = a.AuthMiddleware.Wrap(handler)
156	}
157
158	if a.cfg.ResponseCompression {
159		handler = gziphandler.GzipHandler(handler)
160	}
161
162	if len(methods) == 0 {
163		a.server.HTTP.PathPrefix(prefix).Handler(handler)
164		return
165	}
166	a.server.HTTP.PathPrefix(prefix).Methods(methods...).Handler(handler)
167}
168
169// RegisterAlertmanager registers endpoints associated with the alertmanager. It will only
170// serve endpoints using the legacy http-prefix if it is not run as a single binary.
171func (a *API) RegisterAlertmanager(am *alertmanager.MultitenantAlertmanager, target, apiEnabled bool) {
172	alertmanagerpb.RegisterAlertmanagerServer(a.server.GRPC, am)
173
174	a.indexPage.AddLink(SectionAdminEndpoints, "/multitenant_alertmanager/status", "Alertmanager Status")
175	a.indexPage.AddLink(SectionAdminEndpoints, "/multitenant_alertmanager/ring", "Alertmanager Ring Status")
176	// Ensure this route is registered before the prefixed AM route
177	a.RegisterRoute("/multitenant_alertmanager/status", am.GetStatusHandler(), false, "GET")
178	a.RegisterRoute("/multitenant_alertmanager/configs", http.HandlerFunc(am.ListAllConfigs), false, "GET")
179	a.RegisterRoute("/multitenant_alertmanager/ring", http.HandlerFunc(am.RingHandler), false, "GET", "POST")
180	a.RegisterRoute("/multitenant_alertmanager/delete_tenant_config", http.HandlerFunc(am.DeleteUserConfig), true, "POST")
181
182	// UI components lead to a large number of routes to support, utilize a path prefix instead
183	a.RegisterRoutesWithPrefix(a.cfg.AlertmanagerHTTPPrefix, am, true)
184	level.Debug(a.logger).Log("msg", "api: registering alertmanager", "path_prefix", a.cfg.AlertmanagerHTTPPrefix)
185
186	// MultiTenant Alertmanager Experimental API routes
187	if apiEnabled {
188		a.RegisterRoute("/api/v1/alerts", http.HandlerFunc(am.GetUserConfig), true, "GET")
189		a.RegisterRoute("/api/v1/alerts", http.HandlerFunc(am.SetUserConfig), true, "POST")
190		a.RegisterRoute("/api/v1/alerts", http.HandlerFunc(am.DeleteUserConfig), true, "DELETE")
191	}
192
193	// If the target is Alertmanager, enable the legacy behaviour. Otherwise only enable
194	// the component routed API.
195	if target {
196		a.RegisterRoute("/status", am.GetStatusHandler(), false, "GET")
197		// WARNING: If LegacyHTTPPrefix is an empty string, any other paths added after this point will be
198		// silently ignored by the HTTP service. Therefore, this must be the last route to be configured.
199		a.RegisterRoutesWithPrefix(a.cfg.LegacyHTTPPrefix, am, true)
200	}
201}
202
203// RegisterAPI registers the standard endpoints associated with a running Cortex.
204func (a *API) RegisterAPI(httpPathPrefix string, actualCfg interface{}, defaultCfg interface{}) {
205	a.indexPage.AddLink(SectionAdminEndpoints, "/config", "Current Config (including the default values)")
206	a.indexPage.AddLink(SectionAdminEndpoints, "/config?mode=diff", "Current Config (show only values that differ from the defaults)")
207
208	a.RegisterRoute("/config", a.cfg.configHandler(actualCfg, defaultCfg), false, "GET")
209	a.RegisterRoute("/", indexHandler(httpPathPrefix, a.indexPage), false, "GET")
210	a.RegisterRoute("/debug/fgprof", fgprof.Handler(), false, "GET")
211}
212
213// RegisterRuntimeConfig registers the endpoints associates with the runtime configuration
214func (a *API) RegisterRuntimeConfig(runtimeConfigHandler http.HandlerFunc) {
215	a.indexPage.AddLink(SectionAdminEndpoints, "/runtime_config", "Current Runtime Config (incl. Overrides)")
216	a.indexPage.AddLink(SectionAdminEndpoints, "/runtime_config?mode=diff", "Current Runtime Config (show only values that differ from the defaults)")
217
218	a.RegisterRoute("/runtime_config", runtimeConfigHandler, false, "GET")
219}
220
221// RegisterDistributor registers the endpoints associated with the distributor.
222func (a *API) RegisterDistributor(d *distributor.Distributor, pushConfig distributor.Config) {
223	distributorpb.RegisterDistributorServer(a.server.GRPC, d)
224
225	a.RegisterRoute("/api/v1/push", push.Handler(pushConfig.MaxRecvMsgSize, a.sourceIPs, a.cfg.wrapDistributorPush(d)), true, "POST")
226
227	a.indexPage.AddLink(SectionAdminEndpoints, "/distributor/ring", "Distributor Ring Status")
228	a.indexPage.AddLink(SectionAdminEndpoints, "/distributor/all_user_stats", "Usage Statistics")
229	a.indexPage.AddLink(SectionAdminEndpoints, "/distributor/ha_tracker", "HA Tracking Status")
230
231	a.RegisterRoute("/distributor/ring", d, false, "GET", "POST")
232	a.RegisterRoute("/distributor/all_user_stats", http.HandlerFunc(d.AllUserStatsHandler), false, "GET")
233	a.RegisterRoute("/distributor/ha_tracker", d.HATracker, false, "GET")
234
235	// Legacy Routes
236	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/push"), push.Handler(pushConfig.MaxRecvMsgSize, a.sourceIPs, a.cfg.wrapDistributorPush(d)), true, "POST")
237	a.RegisterRoute("/all_user_stats", http.HandlerFunc(d.AllUserStatsHandler), false, "GET")
238	a.RegisterRoute("/ha-tracker", d.HATracker, false, "GET")
239}
240
241// Ingester is defined as an interface to allow for alternative implementations
242// of ingesters to be passed into the API.RegisterIngester() method.
243type Ingester interface {
244	client.IngesterServer
245	FlushHandler(http.ResponseWriter, *http.Request)
246	ShutdownHandler(http.ResponseWriter, *http.Request)
247	Push(context.Context, *cortexpb.WriteRequest) (*cortexpb.WriteResponse, error)
248}
249
250// RegisterIngester registers the ingesters HTTP and GRPC service
251func (a *API) RegisterIngester(i Ingester, pushConfig distributor.Config) {
252	client.RegisterIngesterServer(a.server.GRPC, i)
253
254	a.indexPage.AddLink(SectionDangerous, "/ingester/flush", "Trigger a Flush of data from Ingester to storage")
255	a.indexPage.AddLink(SectionDangerous, "/ingester/shutdown", "Trigger Ingester Shutdown (Dangerous)")
256	a.RegisterRoute("/ingester/flush", http.HandlerFunc(i.FlushHandler), false, "GET", "POST")
257	a.RegisterRoute("/ingester/shutdown", http.HandlerFunc(i.ShutdownHandler), false, "GET", "POST")
258	a.RegisterRoute("/ingester/push", push.Handler(pushConfig.MaxRecvMsgSize, a.sourceIPs, i.Push), true, "POST") // For testing and debugging.
259
260	// Legacy Routes
261	a.RegisterRoute("/flush", http.HandlerFunc(i.FlushHandler), false, "GET", "POST")
262	a.RegisterRoute("/shutdown", http.HandlerFunc(i.ShutdownHandler), false, "GET", "POST")
263	a.RegisterRoute("/push", push.Handler(pushConfig.MaxRecvMsgSize, a.sourceIPs, i.Push), true, "POST") // For testing and debugging.
264}
265
266// RegisterChunksPurger registers the endpoints associated with the Purger/DeleteStore. They do not exactly
267// match the Prometheus API but mirror it closely enough to justify their routing under the Prometheus
268// component/
269func (a *API) RegisterChunksPurger(store *purger.DeleteStore, deleteRequestCancelPeriod time.Duration) {
270	deleteRequestHandler := purger.NewDeleteRequestHandler(store, deleteRequestCancelPeriod, prometheus.DefaultRegisterer)
271
272	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/admin/tsdb/delete_series"), http.HandlerFunc(deleteRequestHandler.AddDeleteRequestHandler), true, "PUT", "POST")
273	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/admin/tsdb/delete_series"), http.HandlerFunc(deleteRequestHandler.GetAllDeleteRequestsHandler), true, "GET")
274	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/admin/tsdb/cancel_delete_request"), http.HandlerFunc(deleteRequestHandler.CancelDeleteRequestHandler), true, "PUT", "POST")
275
276	// Legacy Routes
277	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/admin/tsdb/delete_series"), http.HandlerFunc(deleteRequestHandler.AddDeleteRequestHandler), true, "PUT", "POST")
278	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/admin/tsdb/delete_series"), http.HandlerFunc(deleteRequestHandler.GetAllDeleteRequestsHandler), true, "GET")
279	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/admin/tsdb/cancel_delete_request"), http.HandlerFunc(deleteRequestHandler.CancelDeleteRequestHandler), true, "PUT", "POST")
280}
281
282func (a *API) RegisterTenantDeletion(api *purger.TenantDeletionAPI) {
283	a.RegisterRoute("/purger/delete_tenant", http.HandlerFunc(api.DeleteTenant), true, "POST")
284	a.RegisterRoute("/purger/delete_tenant_status", http.HandlerFunc(api.DeleteTenantStatus), true, "GET")
285}
286
287// RegisterRuler registers routes associated with the Ruler service.
288func (a *API) RegisterRuler(r *ruler.Ruler) {
289	a.indexPage.AddLink(SectionAdminEndpoints, "/ruler/ring", "Ruler Ring Status")
290	a.RegisterRoute("/ruler/ring", r, false, "GET", "POST")
291
292	// Administrative API, uses authentication to inform which user's configuration to delete.
293	a.RegisterRoute("/ruler/delete_tenant_config", http.HandlerFunc(r.DeleteTenantConfiguration), true, "POST")
294
295	// Legacy Ring Route
296	a.RegisterRoute("/ruler_ring", r, false, "GET", "POST")
297
298	// List all user rule groups
299	a.RegisterRoute("/ruler/rule_groups", http.HandlerFunc(r.ListAllRules), false, "GET")
300
301	ruler.RegisterRulerServer(a.server.GRPC, r)
302}
303
304// RegisterRulerAPI registers routes associated with the Ruler API
305func (a *API) RegisterRulerAPI(r *ruler.API) {
306	// Prometheus Rule API Routes
307	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/rules"), http.HandlerFunc(r.PrometheusRules), true, "GET")
308	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/alerts"), http.HandlerFunc(r.PrometheusAlerts), true, "GET")
309
310	// Ruler API Routes
311	a.RegisterRoute("/api/v1/rules", http.HandlerFunc(r.ListRules), true, "GET")
312	a.RegisterRoute("/api/v1/rules/{namespace}", http.HandlerFunc(r.ListRules), true, "GET")
313	a.RegisterRoute("/api/v1/rules/{namespace}/{groupName}", http.HandlerFunc(r.GetRuleGroup), true, "GET")
314	a.RegisterRoute("/api/v1/rules/{namespace}", http.HandlerFunc(r.CreateRuleGroup), true, "POST")
315	a.RegisterRoute("/api/v1/rules/{namespace}/{groupName}", http.HandlerFunc(r.DeleteRuleGroup), true, "DELETE")
316	a.RegisterRoute("/api/v1/rules/{namespace}", http.HandlerFunc(r.DeleteNamespace), true, "DELETE")
317
318	// Legacy Prometheus Rule API Routes
319	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/rules"), http.HandlerFunc(r.PrometheusRules), true, "GET")
320	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/alerts"), http.HandlerFunc(r.PrometheusAlerts), true, "GET")
321
322	// Legacy Ruler API Routes
323	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/rules"), http.HandlerFunc(r.ListRules), true, "GET")
324	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/rules/{namespace}"), http.HandlerFunc(r.ListRules), true, "GET")
325	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/rules/{namespace}/{groupName}"), http.HandlerFunc(r.GetRuleGroup), true, "GET")
326	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/rules/{namespace}"), http.HandlerFunc(r.CreateRuleGroup), true, "POST")
327	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/rules/{namespace}/{groupName}"), http.HandlerFunc(r.DeleteRuleGroup), true, "DELETE")
328	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/rules/{namespace}"), http.HandlerFunc(r.DeleteNamespace), true, "DELETE")
329}
330
331// RegisterRing registers the ring UI page associated with the distributor for writes.
332func (a *API) RegisterRing(r *ring.Ring) {
333	a.indexPage.AddLink(SectionAdminEndpoints, "/ingester/ring", "Ingester Ring Status")
334	a.RegisterRoute("/ingester/ring", r, false, "GET", "POST")
335
336	// Legacy Route
337	a.RegisterRoute("/ring", r, false, "GET", "POST")
338}
339
340// RegisterStoreGateway registers the ring UI page associated with the store-gateway.
341func (a *API) RegisterStoreGateway(s *storegateway.StoreGateway) {
342	storegatewaypb.RegisterStoreGatewayServer(a.server.GRPC, s)
343
344	a.indexPage.AddLink(SectionAdminEndpoints, "/store-gateway/ring", "Store Gateway Ring")
345	a.RegisterRoute("/store-gateway/ring", http.HandlerFunc(s.RingHandler), false, "GET", "POST")
346}
347
348// RegisterCompactor registers the ring UI page associated with the compactor.
349func (a *API) RegisterCompactor(c *compactor.Compactor) {
350	a.indexPage.AddLink(SectionAdminEndpoints, "/compactor/ring", "Compactor Ring Status")
351	a.RegisterRoute("/compactor/ring", http.HandlerFunc(c.RingHandler), false, "GET", "POST")
352}
353
354type Distributor interface {
355	querier.Distributor
356	UserStatsHandler(w http.ResponseWriter, r *http.Request)
357}
358
359// RegisterQueryable registers the the default routes associated with the querier
360// module.
361func (a *API) RegisterQueryable(
362	queryable storage.SampleAndChunkQueryable,
363	distributor Distributor,
364) {
365	// these routes are always registered to the default server
366	a.RegisterRoute("/api/v1/user_stats", http.HandlerFunc(distributor.UserStatsHandler), true, "GET")
367	a.RegisterRoute("/api/v1/chunks", querier.ChunksHandler(queryable), true, "GET")
368
369	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/user_stats"), http.HandlerFunc(distributor.UserStatsHandler), true, "GET")
370	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/chunks"), querier.ChunksHandler(queryable), true, "GET")
371}
372
373// RegisterQueryAPI registers the Prometheus API routes with the provided handler.
374func (a *API) RegisterQueryAPI(handler http.Handler) {
375	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/read"), handler, true, "POST")
376	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/query"), handler, true, "GET", "POST")
377	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/query_range"), handler, true, "GET", "POST")
378	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/query_exemplars"), handler, true, "GET", "POST")
379	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/labels"), handler, true, "GET", "POST")
380	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/label/{name}/values"), handler, true, "GET")
381	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/series"), handler, true, "GET", "POST", "DELETE")
382	a.RegisterRoute(path.Join(a.cfg.PrometheusHTTPPrefix, "/api/v1/metadata"), handler, true, "GET")
383
384	// Register Legacy Routers
385	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/read"), handler, true, "POST")
386	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/query"), handler, true, "GET", "POST")
387	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/query_range"), handler, true, "GET", "POST")
388	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/query_exemplars"), handler, true, "GET", "POST")
389	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/labels"), handler, true, "GET", "POST")
390	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/label/{name}/values"), handler, true, "GET")
391	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/series"), handler, true, "GET", "POST", "DELETE")
392	a.RegisterRoute(path.Join(a.cfg.LegacyHTTPPrefix, "/api/v1/metadata"), handler, true, "GET")
393}
394
395// RegisterQueryFrontend registers the Prometheus routes supported by the
396// Cortex querier service. Currently this can not be registered simultaneously
397// with the Querier.
398func (a *API) RegisterQueryFrontendHandler(h http.Handler) {
399	a.RegisterQueryAPI(h)
400}
401
402func (a *API) RegisterQueryFrontend1(f *frontendv1.Frontend) {
403	frontendv1pb.RegisterFrontendServer(a.server.GRPC, f)
404}
405
406func (a *API) RegisterQueryFrontend2(f *frontendv2.Frontend) {
407	frontendv2pb.RegisterFrontendForQuerierServer(a.server.GRPC, f)
408}
409
410func (a *API) RegisterQueryScheduler(f *scheduler.Scheduler) {
411	schedulerpb.RegisterSchedulerForFrontendServer(a.server.GRPC, f)
412	schedulerpb.RegisterSchedulerForQuerierServer(a.server.GRPC, f)
413}
414
415// RegisterServiceMapHandler registers the Cortex structs service handler
416// TODO: Refactor this code to be accomplished using the services.ServiceManager
417// or a future module manager #2291
418func (a *API) RegisterServiceMapHandler(handler http.Handler) {
419	a.indexPage.AddLink(SectionAdminEndpoints, "/services", "Service Status")
420	a.RegisterRoute("/services", handler, false, "GET")
421}
422
423func (a *API) RegisterMemberlistKV(handler http.Handler) {
424	a.indexPage.AddLink(SectionAdminEndpoints, "/memberlist", "Memberlist Status")
425	a.RegisterRoute("/memberlist", handler, false, "GET")
426}
427