Skip to content

Commit

Permalink
Support metric label selector for custom metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
astefanutti committed Jul 1, 2019
1 parent 9caf012 commit 71d134e
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 41 deletions.
2 changes: 1 addition & 1 deletion pkg/apiserver/cmapis.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
)

func (s *CustomMetricsAdapterServer) InstallCustomMetricsAPI() error {
groupInfo := genericapiserver.NewDefaultAPIGroupInfo(custom_metrics.GroupName, Scheme, metav1.ParameterCodec, Codecs)
groupInfo := genericapiserver.NewDefaultAPIGroupInfo(custom_metrics.GroupName, Scheme, runtime.NewParameterCodec(Scheme), Codecs)

mainGroupVer := groupInfo.PrioritizedVersions[0]
preferredVersionForDiscovery := metav1.GroupVersionForDiscovery{
Expand Down
16 changes: 8 additions & 8 deletions pkg/apiserver/installer/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ func init() {
&metav1.APIResourceList{},
)

customMetricsGroupInfo = genericapiserver.NewDefaultAPIGroupInfo(custom_metrics.GroupName, Scheme, metav1.ParameterCodec, Codecs)
customMetricsGroupInfo = genericapiserver.NewDefaultAPIGroupInfo(custom_metrics.GroupName, Scheme, runtime.NewParameterCodec(Scheme), Codecs)
customMetricsGroupVersion = customMetricsGroupInfo.PrioritizedVersions[0]
externalMetricsGroupInfo = genericapiserver.NewDefaultAPIGroupInfo(external_metrics.GroupName, Scheme, metav1.ParameterCodec, Codecs)
externalMetricsGroupInfo = genericapiserver.NewDefaultAPIGroupInfo(external_metrics.GroupName, Scheme, runtime.NewParameterCodec(Scheme), Codecs)
externalMetricsGroupVersion = externalMetricsGroupInfo.PrioritizedVersions[0]
}

Expand Down Expand Up @@ -165,7 +165,7 @@ func handleExternalMetrics(prov provider.ExternalMetricsProvider) http.Handler {
Linker: runtime.SelfLinker(meta.NewAccessor()),
},
ResourceLister: provider.NewExternalMetricResourceLister(prov),
Handlers: &CMHandlers{},
Handlers: &EMHandlers{},
}

if err := group.InstallREST(container); err != nil {
Expand All @@ -186,7 +186,7 @@ type fakeCMProvider struct {
metrics []provider.CustomMetricInfo
}

func (p *fakeCMProvider) valuesFor(name types.NamespacedName, info provider.CustomMetricInfo) (string, []custom_metrics.MetricValue, bool) {
func (p *fakeCMProvider) valuesFor(name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (string, []custom_metrics.MetricValue, bool) {
if info.Namespaced {
metricId := name.Namespace + "/" + info.GroupResource.String() + "/" + name.Name + "/" + info.Metric
values, ok := p.namespacedValues[metricId]
Expand All @@ -198,17 +198,17 @@ func (p *fakeCMProvider) valuesFor(name types.NamespacedName, info provider.Cust
}
}

func (p *fakeCMProvider) GetMetricByName(name types.NamespacedName, info provider.CustomMetricInfo) (*custom_metrics.MetricValue, error) {
metricId, values, ok := p.valuesFor(name, info)
func (p *fakeCMProvider) GetMetricByName(name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
metricId, values, ok := p.valuesFor(name, info, metricSelector)
if !ok {
return nil, fmt.Errorf("non-existent metric requested (id: %s)", metricId)
}

return &values[0], nil
}

func (p *fakeCMProvider) GetMetricBySelector(namespace string, selector labels.Selector, info provider.CustomMetricInfo) (*custom_metrics.MetricValueList, error) {
metricId, values, ok := p.valuesFor(types.NamespacedName{Namespace: namespace, Name: "*"}, info)
func (p *fakeCMProvider) GetMetricBySelector(namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
metricId, values, ok := p.valuesFor(types.NamespacedName{Namespace: namespace, Name: "*"}, info, metricSelector)
if !ok {
return nil, fmt.Errorf("non-existent metric requested (id: %s)", metricId)
}
Expand Down
31 changes: 27 additions & 4 deletions pkg/apiserver/installer/cmhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf

kind := fqKindToRegister.Kind

lister := a.group.DynamicStorage.(rest.Lister)
lister := a.group.DynamicStorage.(rest.ListerWithOptions)
list := lister.NewList()
listGVKs, _, err := a.group.Typer.ObjectKinds(list)
if err != nil {
Expand All @@ -64,6 +64,20 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
return err
}

listOptions, _, _ := lister.NewListOptions()
listOptionsInternalKinds, _, err := a.group.Typer.ObjectKinds(listOptions)
if err != nil {
return err
}
listOptionsInternalKind := listOptionsInternalKinds[0]
versionedListExtraOptions, err := a.group.Creater.New(a.group.GroupVersion.WithKind(listOptionsInternalKind.Kind))
if err != nil {
versionedListExtraOptions, err = a.group.Creater.New(optionsExternalVersion.WithKind(listOptionsInternalKind.Kind))
if err != nil {
return err
}
}

nameParam := ws.PathParameter("name", "name of the described resource").DataType("string")
resourceParam := ws.PathParameter("resource", "the name of the resource").DataType("string")
subresourceParam := ws.PathParameter("subresource", "the name of the subresource").DataType("string")
Expand Down Expand Up @@ -135,7 +149,7 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
reqScope.Subresource,
"cluster",
"custom-metrics",
restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout),
restfulListResourceWithOptions(lister, nil, reqScope, false, a.minRequestTimeout),
)

// install the root-scoped route
Expand All @@ -149,6 +163,9 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
if err := addObjectParams(ws, rootScopedRoute, versionedListOptions); err != nil {
return err
}
if err := addObjectParams(ws, rootScopedRoute, versionedListExtraOptions); err != nil {
return err
}
addParams(rootScopedRoute, rootScopedParams)
ws.Route(rootScopedRoute)

Expand All @@ -168,7 +185,7 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
reqScope.Subresource,
"resource",
"custom-metrics",
restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout),
restfulListResourceWithOptions(lister, nil, reqScope, false, a.minRequestTimeout),
)

namespacedRoute := ws.GET(namespacedPath).To(namespacedHandler).
Expand All @@ -181,6 +198,9 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
if err := addObjectParams(ws, namespacedRoute, versionedListOptions); err != nil {
return err
}
if err := addObjectParams(ws, namespacedRoute, versionedListExtraOptions); err != nil {
return err
}
addParams(namespacedRoute, namespacedParams)
ws.Route(namespacedRoute)

Expand All @@ -201,7 +221,7 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
reqScope.Subresource,
"resource",
"custom-metrics",
restfulListResource(lister, nil, reqScope, false, a.minRequestTimeout),
restfulListResourceWithOptions(lister, nil, reqScope, false, a.minRequestTimeout),
)

namespaceSpecificRoute := ws.GET(namespaceSpecificPath).To(namespaceSpecificHandler).
Expand All @@ -214,6 +234,9 @@ func (ch *CMHandlers) registerResourceHandlers(a *MetricsAPIInstaller, ws *restf
if err := addObjectParams(ws, namespaceSpecificRoute, versionedListOptions); err != nil {
return err
}
if err := addObjectParams(ws, namespaceSpecificRoute, versionedListExtraOptions); err != nil {
return err
}
addParams(namespaceSpecificRoute, namespaceSpecificParams)
ws.Route(namespaceSpecificRoute)

Expand Down
6 changes: 6 additions & 0 deletions pkg/apiserver/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,9 @@ func restfulListResource(r rest.Lister, rw rest.Watcher, scope handlers.RequestS
handlers.ListResource(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request)
}
}

func restfulListResourceWithOptions(r rest.ListerWithOptions, rw rest.Watcher, scope handlers.RequestScope, forceWatch bool, minRequestTimeout time.Duration) restful.RouteFunction {
return func(req *restful.Request, res *restful.Response) {
handlers.ListResourceWithOptions(r, rw, scope, forceWatch, minRequestTimeout)(res.ResponseWriter, req.Request)
}
}
12 changes: 12 additions & 0 deletions pkg/provider/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
)

Expand All @@ -47,3 +48,14 @@ func NewMetricNotFoundForError(resource schema.GroupResource, metricName string,
Message: fmt.Sprintf("the server could not find the metric %s for %s %s", metricName, resource.String(), resourceName),
}}
}

// NewMetricNotFoundForError returns a StatusError indicating the given metric could not be found for
// the given named object. It is similar to NewNotFound, but more specialized
func NewMetricNotFoundForSelectorError(resource schema.GroupResource, metricName string, resourceName string, selector labels.Selector) *apierr.StatusError {
return &apierr.StatusError{metav1.Status{
Status: metav1.StatusFailure,
Code: int32(http.StatusNotFound),
Reason: metav1.StatusReasonNotFound,
Message: fmt.Sprintf("the server could not find the metric %s for %s %s with selector %s", metricName, resource.String(), resourceName, selector.String()),
}}
}
4 changes: 2 additions & 2 deletions pkg/provider/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ func (i CustomMetricInfo) Normalized(mapper apimeta.RESTMapper) (normalizedInfo
type CustomMetricsProvider interface {
// GetMetricByName fetches a particular metric for a particular object.
// The namespace will be empty if the metric is root-scoped.
GetMetricByName(name types.NamespacedName, info CustomMetricInfo) (*custom_metrics.MetricValue, error)
GetMetricByName(name types.NamespacedName, info CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error)

// GetMetricBySelector fetches a particular metric for a set of objects matching
// the given label selector. The namespace will be empty if the metric is root-scoped.
GetMetricBySelector(namespace string, selector labels.Selector, info CustomMetricInfo) (*custom_metrics.MetricValueList, error)
GetMetricBySelector(namespace string, selector labels.Selector, info CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error)

// ListAllMetrics provides a list of all available metrics at
// the current time. Note that this is not allowed to return
Expand Down
36 changes: 27 additions & 9 deletions pkg/registry/custom_metrics/reststorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type REST struct {
}

var _ rest.Storage = &REST{}
var _ rest.Lister = &REST{}
var _ rest.ListerWithOptions = &REST{}

func NewREST(cmProvider provider.CustomMetricsProvider) *REST {
return &REST{
Expand All @@ -51,19 +51,37 @@ func (r *REST) New() runtime.Object {
return &custom_metrics.MetricValue{}
}

// Implement Lister
// Implement ListerWithOptions

func (r *REST) NewList() runtime.Object {
return &custom_metrics.MetricValueList{}
}

func (r *REST) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
func (r *REST) NewListOptions() (runtime.Object, bool, string) {
return &custom_metrics.MetricListOptions{}, true, "metricName"
}

func (r *REST) List(ctx context.Context, options *metainternalversion.ListOptions, metricOpts runtime.Object) (runtime.Object, error) {
metricOptions, ok := metricOpts.(*custom_metrics.MetricListOptions)
if !ok {
return nil, fmt.Errorf("invalid options object: %#v", options)
}

// populate the label selector, defaulting to all
selector := labels.Everything()
if options != nil && options.LabelSelector != nil {
selector = options.LabelSelector
}

metricLabelSelector := labels.Everything()
if metricOptions != nil && len(metricOptions.MetricLabelSelector) > 0 {
sel, err := labels.Parse(metricOptions.MetricLabelSelector)
if err != nil {
return nil, err
}
metricLabelSelector = sel
}

// grab the name, if present, from the field selector list options
// (this is how the list handler logic injects it)
// (otherwise we'd have to write a custom list handler)
Expand Down Expand Up @@ -97,18 +115,18 @@ func (r *REST) List(ctx context.Context, options *metainternalversion.ListOption

// handle namespaced and root metrics
if name == "*" {
return r.handleWildcardOp(namespace, groupResource, selector, metricName)
return r.handleWildcardOp(namespace, groupResource, selector, metricName, metricLabelSelector)
} else {
return r.handleIndividualOp(namespace, groupResource, name, metricName)
return r.handleIndividualOp(namespace, groupResource, name, metricName, metricLabelSelector)
}
}

func (r *REST) handleIndividualOp(namespace string, groupResource schema.GroupResource, name string, metricName string) (*custom_metrics.MetricValueList, error) {
func (r *REST) handleIndividualOp(namespace string, groupResource schema.GroupResource, name string, metricName string, metricLabelSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
singleRes, err := r.cmProvider.GetMetricByName(types.NamespacedName{Namespace: namespace, Name: name}, provider.CustomMetricInfo{
GroupResource: groupResource,
Metric: metricName,
Namespaced: namespace != "",
})
}, metricLabelSelector)
if err != nil {
return nil, err
}
Expand All @@ -118,10 +136,10 @@ func (r *REST) handleIndividualOp(namespace string, groupResource schema.GroupRe
}, nil
}

func (r *REST) handleWildcardOp(namespace string, groupResource schema.GroupResource, selector labels.Selector, metricName string) (*custom_metrics.MetricValueList, error) {
func (r *REST) handleWildcardOp(namespace string, groupResource schema.GroupResource, selector labels.Selector, metricName string, metricLabelSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
return r.cmProvider.GetMetricBySelector(namespace, selector, provider.CustomMetricInfo{
GroupResource: groupResource,
Metric: metricName,
Namespaced: namespace != "",
})
}, metricLabelSelector)
}
Loading

0 comments on commit 71d134e

Please sign in to comment.