mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 04:04:00 -06:00
121 lines
3.8 KiB
Go
121 lines
3.8 KiB
Go
package ml
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
)
|
|
|
|
// OutlierCommand implements Command that sends a request to outlier proxy API and converts response to backend.QueryDataResponse
|
|
type OutlierCommand struct {
|
|
config OutlierCommandConfiguration
|
|
appURL string
|
|
interval time.Duration
|
|
}
|
|
|
|
var _ Command = OutlierCommand{}
|
|
|
|
func (c OutlierCommand) Type() string {
|
|
return "outlier"
|
|
}
|
|
|
|
func (c OutlierCommand) DatasourceUID() string {
|
|
return c.config.DatasourceUID
|
|
}
|
|
|
|
// Execute copies the original configuration JSON and appends (overwrites) a field "start_end_attributes" and "grafana_url" to the root object.
|
|
// The value of "start_end_attributes" is JSON object that configures time range and interval.
|
|
// The value of "grafana_url" is app URL that should be used by ML to query the data source.
|
|
// After payload is generated it sends it to POST /proxy/api/v1/outlier endpoint and parses the response.
|
|
// The proxy API normally responds with a structured data. It recognizes status 200 and 204 as successful result.
|
|
// Other statuses are considered unsuccessful and result in error. Tries to extract error from the structured payload.
|
|
// Otherwise, mentions the full message in error
|
|
func (c OutlierCommand) Execute(from, to time.Time, sendRequest func(method string, path string, payload []byte) (response.Response, error)) (*backend.QueryDataResponse, error) {
|
|
payload := outlierRequestBody{
|
|
Data: outlierData{
|
|
Attributes: outlierAttributes{
|
|
OutlierCommandConfiguration: c.config,
|
|
GrafanaURL: c.appURL,
|
|
StartEndAttributes: newTimeRangeAndInterval(from, to, c.interval),
|
|
},
|
|
},
|
|
}
|
|
requestBody, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := sendRequest(http.MethodPost, "/proxy/api/v1/outlier", requestBody)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to call ML API: %w", err)
|
|
}
|
|
if resp == nil {
|
|
return nil, fmt.Errorf("response is nil")
|
|
}
|
|
|
|
// Outlier proxy API usually returns all responses with this body.
|
|
var respData outlierResponse
|
|
|
|
respBody := resp.Body()
|
|
err = json.Unmarshal(respBody, &respData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unexpected format of the response from ML API, status: %d, response: %s", resp.Status(), respBody)
|
|
}
|
|
|
|
if respData.Status == "error" {
|
|
return nil, fmt.Errorf("ML API responded with error: %s", respData.Error)
|
|
}
|
|
|
|
if resp.Status() == http.StatusNoContent {
|
|
return nil, nil
|
|
}
|
|
|
|
if resp.Status() == http.StatusOK {
|
|
return respData.Data, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("unexpected status %d returned by ML API, response: %s", resp.Status(), respBody)
|
|
}
|
|
|
|
// unmarshalOutlierCommand parses the CommandConfiguration.Config, validates data and produces OutlierCommand.
|
|
func unmarshalOutlierCommand(expr CommandConfiguration, appURL string) (*OutlierCommand, error) {
|
|
var cfg OutlierCommandConfiguration
|
|
err := json.Unmarshal(expr.Config, &cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal outlier command: %w", err)
|
|
}
|
|
if len(cfg.DatasourceUID) == 0 {
|
|
return nil, fmt.Errorf("required field `config.datasource_uid` is not specified")
|
|
}
|
|
|
|
if len(cfg.Query) == 0 && len(cfg.QueryParams) == 0 {
|
|
return nil, fmt.Errorf("neither of required fields `config.query_params` or `config.query` are specified")
|
|
}
|
|
|
|
if len(cfg.ResponseType) == 0 {
|
|
return nil, fmt.Errorf("required field `config.response_type` is not specified")
|
|
}
|
|
|
|
if len(cfg.Algorithm) == 0 {
|
|
return nil, fmt.Errorf("required field `config.algorithm` is not specified")
|
|
}
|
|
|
|
interval := defaultInterval
|
|
if expr.IntervalMs != nil {
|
|
i := time.Duration(*expr.IntervalMs) * time.Millisecond
|
|
if i > 0 {
|
|
interval = i
|
|
}
|
|
}
|
|
|
|
return &OutlierCommand{
|
|
config: cfg,
|
|
interval: interval,
|
|
appURL: appURL,
|
|
}, nil
|
|
}
|