Skip to content

Commit

Permalink
Use mdox to sync sample-adapter and doc code snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
olivierlemasle committed Jan 12, 2023
1 parent 95f30fd commit a144605
Show file tree
Hide file tree
Showing 6 changed files with 716 additions and 18 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ verify-generated: update-generated

.PHONY: test
test:
CGO_ENABLED=0 go test ./pkg/...
CGO_ENABLED=0 go test ./...

.PHONY: test-adapter-container
test-adapter-container: build-test-adapter
Expand Down
33 changes: 16 additions & 17 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Put your provider in the `pkg/provider` directory in your repository.

<summary>To get started, you&#39;ll need some imports:</summary>

```go
```go mdox-exec="go run ./hack/snippets -d package,import docs/sample-adapter/pkg/provider/provider.go"
package provider

import (
Expand Down Expand Up @@ -84,7 +84,7 @@ 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
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.ListAllMetrics docs/sample-adapter/pkg/provider/provider.go"
func (p *yourProvider) ListAllMetrics() []provider.CustomMetricInfo {
return []provider.CustomMetricInfo{
// these are mostly arbitrary examples
Expand Down Expand Up @@ -188,7 +188,7 @@ You'll need a handle to a RESTMapper (to map between resources and kinds)
and dynamic client to fetch lists of objects in the cluster, if you don't
already have sufficient information in your metrics pipeline:

```go
```go mdox-exec="go run ./hack/snippets -d type=yourProvider,func=NewProvider docs/sample-adapter/pkg/provider/provider.go"
type yourProvider struct {
client dynamic.Interface
mapper apimeta.RESTMapper
Expand All @@ -214,7 +214,7 @@ backend in these methods.
First, a couple of helpers, which support doing the fake "fetch"
operation, and constructing a result object:

```go
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.valueFor,func=*yourProvider.metricFor docs/sample-adapter/pkg/provider/provider.go"
// valueFor fetches a value from the fake list and increments it.
func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
// normalize the value so that you treat plural resources and singular
Expand All @@ -225,7 +225,7 @@ func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
}

value := p.values[info]
value += 1
value++
p.values[info] = value

return value, nil
Expand All @@ -245,7 +245,7 @@ func (p *yourProvider) metricFor(value int64, name types.NamespacedName, info pr
Name: info.Metric,
},
// you'll want to use the actual timestamp in a real adapter
Timestamp: metav1.Time{time.Now()},
Timestamp: metav1.Time{Time: time.Now()},
Value: *resource.NewMilliQuantity(value*100, resource.DecimalSI),
}, nil
}
Expand All @@ -255,7 +255,7 @@ Then, you'll need to implement the two main methods. The first fetches
a single metric value for one object (for example, for the `object` metric
type in the HorizontalPodAutoscaler):

```go
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.GetMetricByName docs/sample-adapter/pkg/provider/provider.go"
func (p *yourProvider) GetMetricByName(ctx context.Context, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
value, err := p.valueFor(info)
if err != nil {
Expand All @@ -268,7 +268,7 @@ func (p *yourProvider) GetMetricByName(ctx context.Context, name types.Namespace
The second fetches multiple metric values, one for each object in a set
(for example, for the `pods` metric type in the HorizontalPodAutoscaler).

```go
```go mdox-exec="go run ./hack/snippets -d func=*yourProvider.GetMetricBySelector docs/sample-adapter/pkg/provider/provider.go"
func (p *yourProvider) GetMetricBySelector(ctx context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
totalValue, err := p.valueFor(info)
if err != nil {
Expand Down Expand Up @@ -309,11 +309,10 @@ the metrics provided by your provider.

<summary>First, you&#39;ll need a few imports:</summary>

```go
```go mdox-exec="go run ./hack/snippets -d package,import docs/sample-adapter/main.go"
package main

import (
"flag"
"os"

"k8s.io/apimachinery/pkg/util/wait"
Expand All @@ -324,7 +323,7 @@ import (
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"

// make this the path to the provider that you just wrote
yourprov "example.com/youradapter/pkg/provider"
yourprov "sigs.k8s.io/custom-metrics-apiserver/docs/sample-adapter/pkg/provider"
)
```

Expand All @@ -333,7 +332,7 @@ import (
With those out of the way, you can make use of the `basecmd.AdapterBase`
struct to help set up the API server:

```go
```go mdox-exec="go run ./hack/snippets -d type=YourAdapter,func=main docs/sample-adapter/main.go"
type YourAdapter struct {
basecmd.AdapterBase

Expand All @@ -348,10 +347,10 @@ func main() {
// initialize the flags, with one custom flag for the message
cmd := &YourAdapter{}
cmd.Flags().StringVar(&cmd.Message, "msg", "starting adapter...", "startup message")
// make sure you get the klog flags
logs.AddGoFlags(flag.CommandLine)
cmd.Flags().AddGoFlagSet(flag.CommandLine)
cmd.Flags().Parse(os.Args)
logs.AddFlags(cmd.Flags())
if err := cmd.Flags().Parse(os.Args); err != nil {
klog.Fatalf("unable to parse flags: %v", err)
}

provider := cmd.makeProviderOrDie()
cmd.WithCustomMetrics(provider)
Expand All @@ -372,7 +371,7 @@ you might need to pass configuration for connecting to the backing metrics
solution, extra credentials, or advanced configuration. For the provider
you wrote above, the setup code looks something like this:

```go
```go mdox-exec="go run ./hack/snippets -d func=*YourAdapter.makeProviderOrDie docs/sample-adapter/main.go"
func (a *YourAdapter) makeProviderOrDie() provider.CustomMetricsProvider {
client, err := a.DynamicClient()
if err != nil {
Expand Down
74 changes: 74 additions & 0 deletions docs/sample-adapter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2022 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 main

import (
"os"

"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"

basecmd "sigs.k8s.io/custom-metrics-apiserver/pkg/cmd"
"sigs.k8s.io/custom-metrics-apiserver/pkg/provider"

// make this the path to the provider that you just wrote
yourprov "sigs.k8s.io/custom-metrics-apiserver/docs/sample-adapter/pkg/provider"
)

type YourAdapter struct {
basecmd.AdapterBase

// the message printed on startup
Message string
}

func main() {
logs.InitLogs()
defer logs.FlushLogs()

// initialize the flags, with one custom flag for the message
cmd := &YourAdapter{}
cmd.Flags().StringVar(&cmd.Message, "msg", "starting adapter...", "startup message")
logs.AddFlags(cmd.Flags())
if err := cmd.Flags().Parse(os.Args); err != nil {
klog.Fatalf("unable to parse flags: %v", err)
}

provider := cmd.makeProviderOrDie()
cmd.WithCustomMetrics(provider)
// you could also set up external metrics support,
// if your provider supported it:
// cmd.WithExternalMetrics(provider)

klog.Infof(cmd.Message)
if err := cmd.Run(wait.NeverStop); err != nil {
klog.Fatalf("unable to run custom metrics adapter: %v", err)
}
}

func (a *YourAdapter) makeProviderOrDie() provider.CustomMetricsProvider {
client, err := a.DynamicClient()
if err != nil {
klog.Fatalf("unable to construct dynamic client: %v", err)
}

mapper, err := a.RESTMapper()
if err != nil {
klog.Fatalf("unable to construct discovery REST mapper: %v", err)
}

return yourprov.NewProvider(client, mapper)
}
140 changes: 140 additions & 0 deletions docs/sample-adapter/pkg/provider/provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Copyright 2022 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 provider

import (
"context"
"time"

apimeta "k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"k8s.io/metrics/pkg/apis/custom_metrics"

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

type yourProvider struct {
client dynamic.Interface
mapper apimeta.RESTMapper

// just increment values when they're requested
values map[provider.CustomMetricInfo]int64
}

func NewProvider(client dynamic.Interface, mapper apimeta.RESTMapper) provider.CustomMetricsProvider {
return &yourProvider{
client: client,
mapper: mapper,
values: make(map[provider.CustomMetricInfo]int64),
}
}

// valueFor fetches a value from the fake list and increments it.
func (p *yourProvider) valueFor(info provider.CustomMetricInfo) (int64, error) {
// normalize the value so that you treat plural resources and singular
// resources the same (e.g. pods vs pod)
info, _, err := info.Normalized(p.mapper)
if err != nil {
return 0, err
}

value := p.values[info]
value++
p.values[info] = value

return value, nil
}

// metricFor constructs a result for a single metric value.
func (p *yourProvider) metricFor(value int64, name types.NamespacedName, info provider.CustomMetricInfo) (*custom_metrics.MetricValue, error) {
// construct a reference referring to the described object
objRef, err := helpers.ReferenceFor(p.mapper, name, info)
if err != nil {
return nil, err
}

return &custom_metrics.MetricValue{
DescribedObject: objRef,
Metric: custom_metrics.MetricIdentifier{
Name: info.Metric,
},
// you'll want to use the actual timestamp in a real adapter
Timestamp: metav1.Time{Time: time.Now()},
Value: *resource.NewMilliQuantity(value*100, resource.DecimalSI),
}, nil
}

func (p *yourProvider) GetMetricByName(ctx context.Context, name types.NamespacedName, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValue, error) {
value, err := p.valueFor(info)
if err != nil {
return nil, err
}
return p.metricFor(value, name, info)
}

func (p *yourProvider) GetMetricBySelector(ctx context.Context, namespace string, selector labels.Selector, info provider.CustomMetricInfo, metricSelector labels.Selector) (*custom_metrics.MetricValueList, error) {
totalValue, err := p.valueFor(info)
if err != nil {
return nil, err
}

names, err := helpers.ListObjectNames(p.mapper, p.client, namespace, selector, info)
if err != nil {
return nil, err
}

res := make([]custom_metrics.MetricValue, len(names))
for i, name := range names {
// in a real adapter, you might want to consider pre-computing the
// object reference created in metricFor, instead of recomputing it
// for each object.
value, err := p.metricFor(100*totalValue/int64(len(res)), types.NamespacedName{Namespace: namespace, Name: name}, info)
if err != nil {
return nil, err
}
res[i] = *value
}

return &custom_metrics.MetricValueList{
Items: res,
}, nil
}

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,
},
}
}
Loading

0 comments on commit a144605

Please sign in to comment.