grafana/pkg/tsdb/tempo/grpc.go
Andre Pereira c1709c9301
Tempo: TraceQL query response streaming (#69212)
* Refactor Tempo datasource backend to support multiple queryData types.
Added traceId query type that is set when performing the request but doesn't map to a tab.

* WIP data is reaching the frontend

* WIP

* Use channels and goroutines

* Some fixes

* Simplify backend code.
Return traces, metrics, state and error in a dataframe.
Shared state type between FE and BE.
Use getStream() instead of getQueryData()

* Handle errors in frontend

* Update Tempo and use same URL for RPC and HTTP

* Cleanup backend code

* Merge main

* Create grpc client only with host and authenticate

* Create grpc client only with host and authenticate

* Cleanup

* Add streaming to TraceQL Search tab

* Fix merge conflicts

* Added tests for processStream

* make gen-cue

* make gen-cue

* goimports

* lint

* Cleanup go.mod

* Comments

* Addressing PR comments

* Fix streaming for tracel search tab

* Added streaming kill switch as the disableTraceQLStreaming feature toggle

* Small comment

* Fix conflicts

* Correctly capture and send all errors as a DF to client

* Fix infinite error loop

* Fix merge conflicts

* Fix test

* Update deprecated import

* Fix feature toggles gen

* Fix merge conflicts
2023-07-14 15:10:46 +01:00

73 lines
2.2 KiB
Go

package tempo
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"net/url"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/tempo/pkg/tempopb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
// This function creates a new gRPC client to connect to a streaming query service.
// It starts by parsing the URL from the data source settings and extracting the host, since that's what the gRPC connection expects.
// If the URL does not contain a port number, it adds a default port based on the scheme (80 for HTTP and 443 for HTTPS).
// If basic authentication is enabled, it uses TLS transport credentials and sets the basic authentication header for each RPC call.
// Otherwise, it uses insecure credentials.
func newGrpcClient(settings backend.DataSourceInstanceSettings, opts httpclient.Options) (tempopb.StreamingQuerierClient, error) {
parsedUrl, err := url.Parse(settings.URL)
if err != nil {
return nil, err
}
onlyHost := parsedUrl.Host
if !strings.Contains(onlyHost, ":") {
if parsedUrl.Scheme == "http" {
onlyHost += ":80"
} else {
onlyHost += ":443"
}
}
var dialOps []grpc.DialOption
if settings.BasicAuthEnabled {
dialOps = append(dialOps, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})))
dialOps = append(dialOps, grpc.WithPerRPCCredentials(&basicAuth{
Header: basicHeaderForAuth(opts.BasicAuth.User, opts.BasicAuth.Password),
}))
} else {
dialOps = append(dialOps, grpc.WithTransportCredentials(insecure.NewCredentials()))
}
clientConn, err := grpc.Dial(onlyHost, dialOps...)
if err != nil {
return nil, err
}
return tempopb.NewStreamingQuerierClient(clientConn), nil
}
type basicAuth struct {
Header string
}
func (c *basicAuth) GetRequestMetadata(context.Context, ...string) (map[string]string, error) {
return map[string]string{
"Authorization": c.Header,
}, nil
}
func (c *basicAuth) RequireTransportSecurity() bool {
return true
}
func basicHeaderForAuth(username, password string) string {
return fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))))
}