Skip to content
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

feat: add default implementation for providers #151

Merged
merged 2 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 10 additions & 28 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
"k8s.io/metrics/pkg/apis/custom_metrics"

"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/defaults"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/helpers"
)
```
Expand All @@ -76,35 +77,14 @@ type CustomMetricsProvider interface {
First, there's a method for listing all metrics available at any point in
time. It's used to populate the discovery information in the API, so that
clients can know what metrics are available. It's not allowed to fail (it
doesn't return any error), and it should return quickly, so it's suggested
that you update it asynchronously in real-world code.
doesn't return any error), and it should return quickly.

For this walkthrough, you can just return a few statically-named metrics,
two that are namespaced, and one that's on namespaces themselves, and thus
root-scoped:

```go
func (p *yourProvider) ListAllMetrics() []provider.CustomMetricInfo {
return []provider.CustomMetricInfo{
// these are mostly arbitrary examples
{
GroupResource: schema.GroupResource{Group: "", Resource: "pods"},
Metric: "packets-per-second",
Namespaced: true,
},
{
GroupResource: schema.GroupResource{Group: "", Resource: "services"},
Metric: "connections-per-second",
Namespaced: true,
},
{
GroupResource: schema.GroupResource{Group: "", Resource: "namespaces"},
Metric: "work-queue-length",
Namespaced: false,
},
}
}
```
You can list your metrics (asynchronously) and return them on every request.
This is not mandatory because kubernetes can request metric values without
listing them before, but maybe there are some cases where is useful. To
provide a unified solution, a default implementation is provided thanks to
`DefaultCustomMetricsProvider` (and `DefaultExternalMetricsProvider` for
external metrics)

Next, you'll need to implement the methods that actually fetch the
metrics. There are methods for fetching metrics describing arbitrary Kubernetes
Expand Down Expand Up @@ -189,6 +169,8 @@ already have sufficient information in your metrics pipeline:

```go
type yourProvider struct {
defaults.DefaultCustomMetricsProvider
defaults.DefaultExternalMetricsProvider
client dynamic.Interface
mapper apimeta.RESTMapper

Expand Down
8 changes: 3 additions & 5 deletions pkg/apiserver/installer/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
emv1beta1 "k8s.io/metrics/pkg/apis/external_metrics/v1beta1"

"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/defaults"
custommetricstorage "sigs.k8s.io/custom-metrics-apiserver/pkg/registry/custom_metrics"
externalmetricstorage "sigs.k8s.io/custom-metrics-apiserver/pkg/registry/external_metrics"
sampleprovider "sigs.k8s.io/custom-metrics-apiserver/test-adapter/provider"
Expand Down Expand Up @@ -179,7 +180,8 @@ type fakeCMProvider struct {
namespacedValues map[string][]custom_metrics.MetricValue
rootSubsetCounts map[string]int
namespacedSubsetCounts map[string]int
metrics []provider.CustomMetricInfo

defaults.DefaultCustomMetricsProvider
}

func (p *fakeCMProvider) valuesFor(name types.NamespacedName, info provider.CustomMetricInfo) (string, []custom_metrics.MetricValue, bool) {
Expand Down Expand Up @@ -241,10 +243,6 @@ func (p *fakeCMProvider) GetMetricBySelector(_ context.Context, namespace string
return &trimmedValues, nil
}

func (p *fakeCMProvider) ListAllMetrics() []provider.CustomMetricInfo {
return p.metrics
}

type T struct {
Method string
Path string
Expand Down
42 changes: 42 additions & 0 deletions pkg/provider/defaults/default_metric_providers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
Copyright 2023 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Package defaults provides a default implementation of metrics providers.
package defaults

import (
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
)

type DefaultExternalMetricsProvider struct{}

func (em DefaultExternalMetricsProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo {
return []provider.ExternalMetricInfo{
{
Metric: "externalmetric",
JorTurFer marked this conversation as resolved.
Show resolved Hide resolved
},
}
}

type DefaultCustomMetricsProvider struct{}

func (cm DefaultCustomMetricsProvider) ListAllMetrics() []provider.CustomMetricInfo {
return []provider.CustomMetricInfo{
{
Metric: "custommetric",
JorTurFer marked this conversation as resolved.
Show resolved Hide resolved
},
}
}
14 changes: 5 additions & 9 deletions pkg/provider/fake/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ import (
"k8s.io/metrics/pkg/apis/external_metrics"

"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/defaults"
)

type fakeProvider struct{}
type fakeProvider struct {
defaults.DefaultCustomMetricsProvider
defaults.DefaultExternalMetricsProvider
}

func (*fakeProvider) GetMetricByName(_ context.Context, _ types.NamespacedName, _ provider.CustomMetricInfo, _ labels.Selector) (*custom_metrics.MetricValue, error) {
return &custom_metrics.MetricValue{}, nil
Expand All @@ -38,18 +42,10 @@ func (*fakeProvider) GetMetricBySelector(_ context.Context, _ string, _ labels.S
return &custom_metrics.MetricValueList{}, nil
}

func (*fakeProvider) ListAllMetrics() []provider.CustomMetricInfo {
return []provider.CustomMetricInfo{}
}

func (*fakeProvider) GetExternalMetric(_ context.Context, _ string, _ labels.Selector, _ provider.ExternalMetricInfo) (*external_metrics.ExternalMetricValueList, error) {
return &external_metrics.ExternalMetricValueList{}, nil
}

func (*fakeProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo {
return []provider.ExternalMetricInfo{}
}

// NewProvider creates a fake implementation of MetricsProvider.
func NewProvider() provider.MetricsProvider {
return &fakeProvider{}
Expand Down
9 changes: 7 additions & 2 deletions pkg/provider/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ type CustomMetricsProvider interface {

// ListAllMetrics provides a list of all available metrics at
// the current time. Note that this is not allowed to return
// an error, so it is recommended that implementors cache and
// periodically update this list, instead of querying every time.
// an error, so it is recommended that implementors use the
// default implementation provided by DefaultCustomMetricsProvider.
ListAllMetrics() []CustomMetricInfo
}

Expand All @@ -104,6 +104,11 @@ type CustomMetricsProvider interface {
type ExternalMetricsProvider interface {
GetExternalMetric(ctx context.Context, namespace string, metricSelector labels.Selector, info ExternalMetricInfo) (*external_metrics.ExternalMetricValueList, error)

// ListAllExternalMetrics provides a list of all available
// external metrics at the current time.
// Note that this is not allowed to return an error, so it is
// recommended that implementors use the default implementation
// provided by DefaultExternalMetricsProvider.
ListAllExternalMetrics() []ExternalMetricInfo
}

Expand Down
33 changes: 3 additions & 30 deletions test-adapter/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"k8s.io/metrics/pkg/apis/external_metrics"

"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/defaults"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider/helpers"
)

Expand Down Expand Up @@ -105,6 +106,8 @@ var _ provider.MetricsProvider = &testingProvider{}

// testingProvider is a sample implementation of provider.MetricsProvider which stores a map of fake metrics
type testingProvider struct {
defaults.DefaultCustomMetricsProvider
defaults.DefaultExternalMetricsProvider
client dynamic.Interface
mapper apimeta.RESTMapper

Expand Down Expand Up @@ -308,25 +311,6 @@ func (p *testingProvider) GetMetricBySelector(_ context.Context, namespace strin
return p.metricsFor(namespace, selector, info, metricSelector)
}

func (p *testingProvider) ListAllMetrics() []provider.CustomMetricInfo {
p.valuesLock.RLock()
defer p.valuesLock.RUnlock()

// Get unique CustomMetricInfos from wrapper CustomMetricResources
infos := make(map[provider.CustomMetricInfo]struct{})
for resource := range p.values {
infos[resource.CustomMetricInfo] = struct{}{}
}

// Build slice of CustomMetricInfos to be returns
metrics := make([]provider.CustomMetricInfo, 0, len(infos))
for info := range infos {
metrics = append(metrics, info)
}

return metrics
}

func (p *testingProvider) GetExternalMetric(_ context.Context, _ string, metricSelector labels.Selector, info provider.ExternalMetricInfo) (*external_metrics.ExternalMetricValueList, error) {
p.valuesLock.RLock()
defer p.valuesLock.RUnlock()
Expand All @@ -344,14 +328,3 @@ func (p *testingProvider) GetExternalMetric(_ context.Context, _ string, metricS
Items: matchingMetrics,
}, nil
}

func (p *testingProvider) ListAllExternalMetrics() []provider.ExternalMetricInfo {
p.valuesLock.RLock()
defer p.valuesLock.RUnlock()

externalMetricsInfo := []provider.ExternalMetricInfo{}
for _, metric := range p.externalMetrics {
externalMetricsInfo = append(externalMetricsInfo, metric.info)
}
return externalMetricsInfo
}