-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update chart to add metrics sql support #4277
Conversation
runtime/server/queries_charts.go
Outdated
chartName := req.PathValue("chart_name") | ||
// TODO: is a separate auth needed? | ||
if !auth.GetClaims(ctx).CanInstance(instanceId, auth.ReadMetrics) { | ||
return ErrForbidden |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns grpc error which I think will map to a bad request. Should use httputil.Error
.
runtime/server/queries_charts.go
Outdated
|
||
ch, err := resolveChart(ctx, s.runtime, instanceId, chartName) | ||
if err != nil { | ||
return err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, it will again map to http.StatusBadRequest
.
runtime/server/queries_charts.go
Outdated
@@ -0,0 +1,116 @@ | |||
package server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
may be this should be charts_api.go
>
runtime/server/server.go
Outdated
@@ -208,6 +208,9 @@ func (s *Server) HTTPHandler(ctx context.Context, registerAdditionalHandlers fun | |||
// custom API handler | |||
observability.MuxHandle(httpMux, "/v1/instances/{instance_id}/api/{name...}", observability.Middleware("runtime", s.logger, auth.HTTPMiddleware(s.aud, httputil.Handler(s.apiForName)))) | |||
|
|||
// custom Chart Data handler | |||
observability.MuxHandle(httpMux, "/v1/instances/{instance_id}/charts/{chart_name...}", observability.Middleware("runtime", s.logger, auth.HTTPMiddleware(s.aud, httputil.Handler(s.GetChartData)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Path also had /data
suffix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes will add that.
runtime/server/queries_charts.go
Outdated
func resolveChart(ctx context.Context, rt *runtime.Runtime, instanceID, name string) (*runtimev1.ChartSpec, error) { | ||
ctrl, err := rt.Controller(ctx, instanceID) | ||
if err != nil { | ||
return nil, status.Error(codes.InvalidArgument, err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should not return grpc errors otherwise everything gets mapped to bad request (which although is correct in this case). See httputil.WriteError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya updating this method to just return the error as is. The caller will handle the not found and to use httputil.Errorf
runtime/server/queries_charts.go
Outdated
|
||
res, err := ctrl.Get(ctx, &runtimev1.ResourceName{Kind: runtime.ResourceKindChart, Name: name}, false) | ||
if err != nil { | ||
return nil, status.Error(codes.InvalidArgument, err.Error()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should handle notfound
case separately since any other error apart from that is an internal error.
e8cdf75
to
0b715c2
Compare
string metrics_sql = 2; | ||
string api = 3; | ||
map<string, string> args = 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of putting the YAML properties here, I would suggest having data_resolver
and data_resolver_properties
mirroring the APISpec
. And then we should have one common YAML structure for parsing a data:
clause (which we will want to use in all of APIs, charts, alerts, reports, etc.).
This would be similar to the parseScheduleYAML
function (see here) in the compiler. We might have this:
type DataYAML struct {
SQL string `...`
MetricsSQL string `...`
// Can add others. Like "api" for referencing another existing API.
}
func parseDataYAML(raw *DataYAML) (resolver string, resolverProps map[string]any, error) {
...
}
// In charts:
type ChartYaml struct {
// ...
Data *DataYAML `yaml:"data"`
}
// In alerts:
type AlertYAML struct {
// ...
Data *DataYAML `yaml:"data"` // This will eventually replace/deprecate the "query:" property
}
// In APIs:
type APIYAML struct {
// ...
Data *DataYAML `yaml:",inline"` // Inline since API's don't nest under "data":
}
// etc.
47841d8
to
8ea285b
Compare
if (tmp.Data.MetricsSQL == "" && tmp.Data.API == "") || (tmp.Data.MetricsSQL != "" && tmp.Data.API != "") { | ||
return fmt.Errorf("exactly one of metrics_sql or api should be set") | ||
if tmp.Data == nil { | ||
return errors.New("data is mandatory") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return errors.New("data is mandatory") | |
return errors.New(`missing required property "data"`) |
// Convert resolver properties to structpb.Struct before inserting the resource (since we can't return errors after that point) | ||
resolverPropsPB, err := structpb.NewStruct(resolverProps) | ||
if err != nil { | ||
return fmt.Errorf("encountered invalid property type: %w", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, can we also move this into the parseDataYAML
function, and return a structpb
value instead of a map? Since otherwise, we will need to duplicate it every place that has a data section.
runtime/server/charts_api.go
Outdated
switch resolver { | ||
case "Metrics": | ||
resolverPropsPB, err := structpb.NewStruct(map[string]interface{}{ | ||
"sql": resolverProps["sql"], | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// TODO: are other fields needed? | ||
api = &runtimev1.API{ | ||
Spec: &runtimev1.APISpec{ | ||
Resolver: "Metrics", | ||
ResolverProperties: resolverPropsPB, | ||
}, | ||
} | ||
|
||
case "API": | ||
apiName, ok := resolverProps["api"].(string) | ||
if !ok { | ||
return nil, errors.New("api name is missing") | ||
} | ||
api, err = rt.APIForName(ctx, instanceID, apiName) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
default: | ||
return nil, errors.New("unknown resolver") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest refactoring runtime.APIResolverOptions
to take Resolver
and ResolverProperties
as direct options instead of an API
.
Since the point is to make the resolvers generic enough that all the code between the parser and the actual resolver doesn't need to know about the different available options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make that work, you probably also need to add a new resolver called "API" that proxies to another resolver (and maybe checks to avoid infinite recursion if you want to be fancy?)
This PR replaces the old
queryName
andqueryArgs
with metrics sql support.New format,
New API,
Note: if any chart was created, the db file will have to be deleted.