mirror of https://github.com/grafana/grafana
Jaeger: run metadata requests through the backend (#100337)
* run metadata requests throught the backend * fix tests * add tests to backend * fix lintpull/101379/head
parent
fcdbb5887d
commit
af0e388622
@ -0,0 +1,69 @@ |
||||
package jaeger |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"net/http" |
||||
"strings" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend" |
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log" |
||||
) |
||||
|
||||
func (s *Service) registerResourceRoutes() *http.ServeMux { |
||||
router := http.NewServeMux() |
||||
router.HandleFunc("GET /services", s.withDatasourceHandlerFunc(getServicesHandler)) |
||||
router.HandleFunc("GET /services/{service}/operations", s.withDatasourceHandlerFunc(getOperationsHandler)) |
||||
return router |
||||
} |
||||
|
||||
func (s *Service) withDatasourceHandlerFunc(getHandler func(d *datasourceInfo) http.HandlerFunc) func(rw http.ResponseWriter, r *http.Request) { |
||||
return func(rw http.ResponseWriter, r *http.Request) { |
||||
client, err := s.getDSInfo(r.Context(), backend.PluginConfigFromContext(r.Context())) |
||||
if err != nil { |
||||
writeResponse(nil, errors.New("error getting data source information from context"), rw, client.JaegerClient.logger) |
||||
return |
||||
} |
||||
h := getHandler(client) |
||||
h.ServeHTTP(rw, r) |
||||
} |
||||
} |
||||
|
||||
func getServicesHandler(ds *datasourceInfo) http.HandlerFunc { |
||||
return func(rw http.ResponseWriter, r *http.Request) { |
||||
services, err := ds.JaegerClient.Services() |
||||
writeResponse(services, err, rw, ds.JaegerClient.logger) |
||||
} |
||||
} |
||||
|
||||
func getOperationsHandler(ds *datasourceInfo) http.HandlerFunc { |
||||
return func(rw http.ResponseWriter, r *http.Request) { |
||||
service := strings.TrimSpace(r.PathValue("service")) |
||||
operations, err := ds.JaegerClient.Operations(service) |
||||
writeResponse(operations, err, rw, ds.JaegerClient.logger) |
||||
} |
||||
} |
||||
|
||||
func writeResponse(res interface{}, err error, rw http.ResponseWriter, logger log.Logger) { |
||||
if err != nil { |
||||
// This is used for resource calls, we don't need to add actual error message, but we should log it
|
||||
logger.Warn("An error occurred while doing a resource call", "error", err) |
||||
http.Error(rw, "An error occurred within the plugin", http.StatusInternalServerError) |
||||
return |
||||
} |
||||
// Response should not be string, but just in case, handle it
|
||||
if str, ok := res.(string); ok { |
||||
rw.Header().Set("Content-Type", "text/plain") |
||||
_, _ = rw.Write([]byte(str)) |
||||
return |
||||
} |
||||
b, err := json.Marshal(res) |
||||
if err != nil { |
||||
// This is used for resource calls, we don't need to add actual error message, but we should log it
|
||||
logger.Warn("An error occurred while processing response from resource call", "error", err) |
||||
http.Error(rw, "An error occurred within the plugin", http.StatusInternalServerError) |
||||
return |
||||
} |
||||
rw.Header().Set("Content-Type", "application/json") |
||||
_, _ = rw.Write(b) |
||||
} |
||||
@ -0,0 +1,166 @@ |
||||
package jaeger |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"errors" |
||||
"net/http" |
||||
"net/http/httptest" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log" |
||||
"github.com/stretchr/testify/assert" |
||||
) |
||||
|
||||
func TestJaegerClient_Services(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
mockResponse string |
||||
mockStatusCode int |
||||
mockStatus string |
||||
expectedResult []string |
||||
expectError bool |
||||
expectedError error |
||||
}{ |
||||
{ |
||||
name: "Successful response", |
||||
mockResponse: `{"data": ["service1", "service2"], "total": 2, "limit": 0, "offset": 0}`, |
||||
mockStatusCode: http.StatusOK, |
||||
mockStatus: "OK", |
||||
expectedResult: []string{"service1", "service2"}, |
||||
expectError: false, |
||||
expectedError: nil, |
||||
}, |
||||
{ |
||||
name: "Non-200 response", |
||||
mockResponse: "", |
||||
mockStatusCode: http.StatusInternalServerError, |
||||
mockStatus: "Internal Server Error", |
||||
expectedResult: []string{}, |
||||
expectError: true, |
||||
expectedError: errors.New("Internal Server Error"), |
||||
}, |
||||
{ |
||||
name: "Invalid JSON response", |
||||
mockResponse: `{invalid json`, |
||||
mockStatusCode: http.StatusOK, |
||||
mockStatus: "OK", |
||||
expectedResult: []string{}, |
||||
expectError: true, |
||||
expectedError: &json.SyntaxError{}, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(tt.mockStatusCode) |
||||
_, _ = w.Write([]byte(tt.mockResponse)) |
||||
})) |
||||
defer server.Close() |
||||
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger()) |
||||
assert.NoError(t, err) |
||||
|
||||
services, err := client.Services() |
||||
|
||||
if tt.expectError { |
||||
assert.Error(t, err) |
||||
if tt.expectedError != nil { |
||||
assert.IsType(t, tt.expectedError, err) |
||||
} |
||||
} else { |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, tt.expectedResult, services) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestJaegerClient_Operations(t *testing.T) { |
||||
tests := []struct { |
||||
name string |
||||
service string |
||||
mockResponse string |
||||
mockStatusCode int |
||||
mockStatus string |
||||
expectedResult []string |
||||
expectError bool |
||||
expectedError error |
||||
}{ |
||||
{ |
||||
name: "Successful response", |
||||
service: "test-service", |
||||
mockResponse: `{"data": ["operation1", "operation2"], "total": 2, "limit": 0, "offset": 0}`, |
||||
mockStatusCode: http.StatusOK, |
||||
mockStatus: "OK", |
||||
expectedResult: []string{"operation1", "operation2"}, |
||||
expectError: false, |
||||
expectedError: nil, |
||||
}, |
||||
{ |
||||
name: "Non-200 response", |
||||
service: "test-service", |
||||
mockResponse: "", |
||||
mockStatusCode: http.StatusInternalServerError, |
||||
mockStatus: "Internal Server Error", |
||||
expectedResult: []string{}, |
||||
expectError: true, |
||||
expectedError: errors.New("Internal Server Error"), |
||||
}, |
||||
{ |
||||
name: "Invalid JSON response", |
||||
service: "test-service", |
||||
mockResponse: `{invalid json`, |
||||
mockStatusCode: http.StatusOK, |
||||
mockStatus: "OK", |
||||
expectedResult: []string{}, |
||||
expectError: true, |
||||
expectedError: &json.SyntaxError{}, |
||||
}, |
||||
{ |
||||
name: "Service with special characters", |
||||
service: "test/service:1", |
||||
mockResponse: `{"data": ["operation1"], "total": 1, "limit": 0, "offset": 0}`, |
||||
mockStatusCode: http.StatusOK, |
||||
mockStatus: "OK", |
||||
expectedResult: []string{"operation1"}, |
||||
expectError: false, |
||||
expectedError: nil, |
||||
}, |
||||
{ |
||||
name: "Empty service", |
||||
service: "", |
||||
mockResponse: `{"data": [], "total": 0, "limit": 0, "offset": 0}`, |
||||
mockStatusCode: http.StatusOK, |
||||
mockStatus: "OK", |
||||
expectedResult: []string{}, |
||||
expectError: false, |
||||
expectedError: nil, |
||||
}, |
||||
} |
||||
|
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
w.WriteHeader(tt.mockStatusCode) |
||||
_, _ = w.Write([]byte(tt.mockResponse)) |
||||
})) |
||||
defer server.Close() |
||||
|
||||
client, err := New(server.URL, server.Client(), log.NewNullLogger()) |
||||
assert.NoError(t, err) |
||||
|
||||
operations, err := client.Operations(tt.service) |
||||
|
||||
if tt.expectError { |
||||
assert.Error(t, err) |
||||
if tt.expectedError != nil { |
||||
assert.IsType(t, tt.expectedError, err) |
||||
} |
||||
} else { |
||||
assert.NoError(t, err) |
||||
assert.Equal(t, tt.expectedResult, operations) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
Loading…
Reference in new issue