mirror of https://github.com/grafana/grafana
Aggregator: Prepare for new handlers (#92030)
parent
135f6571a9
commit
def8104e74
@ -0,0 +1,67 @@ |
||||
package apiserver |
||||
|
||||
import ( |
||||
"net/http" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
grafanasemconv "github.com/grafana/grafana/pkg/semconv" |
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" |
||||
"k8s.io/apiserver/pkg/endpoints/request" |
||||
"k8s.io/component-base/tracing" |
||||
|
||||
aggregationv0alpha1 "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/aggregator/apiserver/plugin" |
||||
) |
||||
|
||||
// dataPlaneServiceHandler provides a http.Handler which will proxy traffic to a plugin client.
|
||||
type dataPlaneServiceHandler struct { |
||||
localDelegate http.Handler |
||||
client plugin.PluginClient |
||||
pluginContextProvider plugin.PluginContextProvider |
||||
handlingInfo atomic.Value |
||||
} |
||||
|
||||
type handlingInfo struct { |
||||
name string |
||||
handler http.Handler |
||||
} |
||||
|
||||
func (r *dataPlaneServiceHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
||||
value := r.handlingInfo.Load() |
||||
if value == nil { |
||||
r.localDelegate.ServeHTTP(w, req) |
||||
return |
||||
} |
||||
handlingInfo := value.(handlingInfo) |
||||
|
||||
namespace, _ := request.NamespaceFrom(req.Context()) |
||||
ctx, span := tracing.Start( |
||||
req.Context(), |
||||
"grafana-aggregator", |
||||
grafanasemconv.K8sDataplaneserviceName(handlingInfo.name), |
||||
semconv.K8SNamespaceName(namespace), |
||||
semconv.HTTPMethod(req.Method), |
||||
semconv.HTTPURL(req.URL.String()), |
||||
) |
||||
// log if the span has not ended after a minute
|
||||
defer span.End(time.Minute) |
||||
|
||||
handlingInfo.handler.ServeHTTP(w, req.WithContext(ctx)) |
||||
} |
||||
|
||||
func (r *dataPlaneServiceHandler) updateDataPlaneService(dataplaneService *aggregationv0alpha1.DataPlaneService) { |
||||
newInfo := handlingInfo{ |
||||
name: dataplaneService.Name, |
||||
} |
||||
|
||||
// currently only plugin handlers are supported
|
||||
newInfo.handler = plugin.NewPluginHandler( |
||||
r.client, |
||||
*dataplaneService, |
||||
r.pluginContextProvider, |
||||
r.localDelegate, |
||||
) |
||||
|
||||
r.handlingInfo.Store(newInfo) |
||||
} |
@ -1,102 +0,0 @@ |
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Provenance-includes-location: https://github.com/kubernetes/kube-aggregator/blob/master/pkg/apiserver/handler_proxy.go
|
||||
// Provenance-includes-license: Apache-2.0
|
||||
// Provenance-includes-copyright: The Kubernetes Authors.
|
||||
|
||||
package apiserver |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||
semconv "go.opentelemetry.io/otel/semconv/v1.21.0" |
||||
"k8s.io/apimachinery/pkg/runtime" |
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" |
||||
"k8s.io/apiserver/pkg/endpoints/request" |
||||
"k8s.io/component-base/tracing" |
||||
|
||||
aggregationv0alpha1 "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" |
||||
grafanasemconv "github.com/grafana/grafana/pkg/semconv" |
||||
) |
||||
|
||||
type PluginClient interface { |
||||
backend.QueryDataHandler |
||||
backend.StreamHandler |
||||
backend.AdmissionHandler |
||||
backend.CallResourceHandler |
||||
} |
||||
|
||||
type PluginContextProvider interface { |
||||
GetPluginContext(ctx context.Context, pluginID, uid string) (backend.PluginContext, error) |
||||
} |
||||
|
||||
// proxyHandler provides a http.Handler which will proxy traffic to a plugin client.
|
||||
type proxyHandler struct { |
||||
localDelegate http.Handler |
||||
client PluginClient |
||||
pluginContextProvider PluginContextProvider |
||||
handlingInfo atomic.Value |
||||
} |
||||
|
||||
type proxyHandlingInfo struct { |
||||
name string |
||||
handler *pluginHandler |
||||
} |
||||
|
||||
func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
||||
value := r.handlingInfo.Load() |
||||
if value == nil { |
||||
r.localDelegate.ServeHTTP(w, req) |
||||
return |
||||
} |
||||
handlingInfo := value.(proxyHandlingInfo) |
||||
|
||||
namespace, _ := request.NamespaceFrom(req.Context()) |
||||
ctx, span := tracing.Start( |
||||
req.Context(), |
||||
"grafana-aggregator", |
||||
grafanasemconv.K8sDataplaneserviceName(handlingInfo.name), |
||||
semconv.K8SNamespaceName(namespace), |
||||
semconv.HTTPMethod(req.Method), |
||||
semconv.HTTPURL(req.URL.String()), |
||||
) |
||||
// log if the span has not ended after a minute
|
||||
defer span.End(time.Minute) |
||||
|
||||
handlingInfo.handler.ServeHTTP(w, req.WithContext(ctx)) |
||||
} |
||||
|
||||
func (r *proxyHandler) updateDataPlaneService(dataplaneService *aggregationv0alpha1.DataPlaneService) { |
||||
newInfo := proxyHandlingInfo{ |
||||
name: dataplaneService.Name, |
||||
} |
||||
|
||||
newInfo.handler = newPluginHandler( |
||||
r.client, |
||||
*dataplaneService, |
||||
r.pluginContextProvider, |
||||
r.localDelegate, |
||||
) |
||||
|
||||
r.handlingInfo.Store(newInfo) |
||||
} |
||||
|
||||
// responder implements rest.Responder for assisting a connector in writing objects or errors.
|
||||
type responder struct { |
||||
w http.ResponseWriter |
||||
} |
||||
|
||||
// TODO this should properly handle content type negotiation
|
||||
// if the caller asked for protobuf and you write JSON bad things happen.
|
||||
func (r *responder) Object(statusCode int, obj runtime.Object) { |
||||
responsewriters.WriteRawJSON(statusCode, obj, r.w) |
||||
} |
||||
|
||||
func (r *responder) Error(_ http.ResponseWriter, req *http.Request, err error) { |
||||
tracing.SpanFromContext(req.Context()).RecordError(err) |
||||
s := responsewriters.ErrorToAPIStatus(err) |
||||
r.Object(http.StatusInternalServerError, s) |
||||
} |
@ -0,0 +1,83 @@ |
||||
package plugin |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
"path" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||
|
||||
aggregationv0alpha1 "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" |
||||
) |
||||
|
||||
type PluginClient interface { |
||||
backend.QueryDataHandler |
||||
backend.StreamHandler |
||||
backend.AdmissionHandler |
||||
backend.CallResourceHandler |
||||
} |
||||
|
||||
type PluginContextProvider interface { |
||||
GetPluginContext(ctx context.Context, pluginID, uid string) (backend.PluginContext, error) |
||||
} |
||||
|
||||
type PluginHandler struct { |
||||
mux *http.ServeMux |
||||
delegate http.Handler |
||||
|
||||
client PluginClient |
||||
pluginContextProvider PluginContextProvider |
||||
|
||||
dataplaneService aggregationv0alpha1.DataPlaneService |
||||
} |
||||
|
||||
func NewPluginHandler( |
||||
client PluginClient, |
||||
dataplaneService aggregationv0alpha1.DataPlaneService, |
||||
pluginContextProvider PluginContextProvider, |
||||
delegate http.Handler, |
||||
) *PluginHandler { |
||||
h := &PluginHandler{ |
||||
mux: http.NewServeMux(), |
||||
delegate: delegate, |
||||
client: client, |
||||
pluginContextProvider: pluginContextProvider, |
||||
dataplaneService: dataplaneService, |
||||
} |
||||
h.registerRoutes() |
||||
return h |
||||
} |
||||
|
||||
func (h *PluginHandler) registerRoutes() { |
||||
proxyPath := proxyPathBuilder(h.dataplaneService.Spec.Group, h.dataplaneService.Spec.Version) |
||||
|
||||
for _, service := range h.dataplaneService.Spec.Services { |
||||
switch service.Type { |
||||
case aggregationv0alpha1.AdmissionControlServiceType: |
||||
// TODO: implement in future PR
|
||||
case aggregationv0alpha1.ConversionServiceType: |
||||
// TODO: implement in future PR
|
||||
case aggregationv0alpha1.DataSourceProxyServiceType: |
||||
// TODO: implement in future PR
|
||||
case aggregationv0alpha1.QueryServiceType: |
||||
h.mux.Handle(proxyPath("/namespaces/{namespace}/connections/{uid}/query"), h.QueryDataHandler()) |
||||
case aggregationv0alpha1.RouteServiceType: |
||||
// TODO: implement in future PR
|
||||
case aggregationv0alpha1.StreamServiceType: |
||||
// TODO: implement in future PR
|
||||
} |
||||
} |
||||
|
||||
// fallback to the delegate
|
||||
h.mux.Handle("/", h.delegate) |
||||
} |
||||
|
||||
func (h *PluginHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
||||
h.mux.ServeHTTP(w, req) |
||||
} |
||||
|
||||
func proxyPathBuilder(group, version string) func(string) string { |
||||
return func(suffix string) string { |
||||
return path.Join("/apis", group, version, suffix) |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
package util |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"k8s.io/apimachinery/pkg/runtime" |
||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" |
||||
"k8s.io/component-base/tracing" |
||||
) |
||||
|
||||
// Responder implements rest.Responder for assisting a connector in writing objects or errors.
|
||||
type Responder struct { |
||||
ResponseWriter http.ResponseWriter |
||||
} |
||||
|
||||
func (r Responder) Object(statusCode int, obj runtime.Object) { |
||||
responsewriters.WriteRawJSON(statusCode, obj, r.ResponseWriter) |
||||
} |
||||
|
||||
func (r *Responder) Error(_ http.ResponseWriter, req *http.Request, err error) { |
||||
tracing.SpanFromContext(req.Context()).RecordError(err) |
||||
s := responsewriters.ErrorToAPIStatus(err) |
||||
r.Object(http.StatusInternalServerError, s) |
||||
} |
Loading…
Reference in new issue