mirror of
https://github.com/zalando-incubator/kube-metrics-adapter.git
synced 2025-05-17 10:45:21 +00:00
Compare commits
21 Commits
gen-openap
...
update-dep
Author | SHA1 | Date | |
---|---|---|---|
8d7dc13bbd | |||
20f0042017 | |||
c732851b6b | |||
2e09132f95 | |||
95f22dc398 | |||
eed5c78fb3 | |||
e61071a36d | |||
c108fab55a | |||
be7567efea | |||
b677e814be | |||
deec4727ee | |||
cf369639d8 | |||
262a35c2ec | |||
607386834b | |||
4bdce4da4d | |||
801e5d7a47 | |||
4df21ae2b4 | |||
7f639baeee | |||
88ddb6f10e | |||
d9cf2e0858 | |||
c3b18e784b |
35
.github/CODEOWNERS
vendored
35
.github/CODEOWNERS
vendored
@ -1,35 +0,0 @@
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo.
|
||||
* @arjunrn
|
||||
|
||||
|
||||
|
||||
# Samples for assigning codeowners below:
|
||||
# Order is important; the last matching pattern takes the most
|
||||
# precedence. When someone opens a pull request that only
|
||||
# modifies JS files, only @js-owner and not the global
|
||||
# owner(s) will be requested for a review.
|
||||
# *.js @js-owner
|
||||
|
||||
# You can also use email addresses if you prefer. They'll be
|
||||
# used to look up users just like we do for commit author
|
||||
# emails.
|
||||
# *.go docs@example.com
|
||||
|
||||
# In this example, @doctocat owns any files in the build/logs
|
||||
# directory at the root of the repository and any of its
|
||||
# subdirectories.
|
||||
# /build/logs/ @doctocat
|
||||
|
||||
# The `docs/*` pattern will match files like
|
||||
# `docs/getting-started.md` but not further nested files like
|
||||
# `docs/build-app/troubleshooting.md`.
|
||||
# docs/* docs@example.com
|
||||
|
||||
# In this example, @octocat owns any file in an apps directory
|
||||
# anywhere in your repository.
|
||||
# apps/ @octocat
|
||||
|
||||
# In this example, @doctocat owns any file in the `/docs`
|
||||
# directory in the root of your repository.
|
||||
# /docs/ @doctocat
|
14
.github/dependabot.yml
vendored
Normal file
14
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "07:00"
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: docker
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "07:00"
|
||||
open-pull-requests-limit: 10
|
@ -1,3 +1,2 @@
|
||||
Mikkel Larsen <mikkel.larsen@zalando.de>
|
||||
Arjun Naik <arjun.naik@zalando.de>
|
||||
Team Teapot <team-teapot@zalando.de>
|
||||
|
44
Makefile
44
Makefile
@ -1,22 +1,22 @@
|
||||
.PHONY: clean test check build.local build.linux build.osx build.docker build.push
|
||||
|
||||
BINARY ?= kube-metrics-adapter
|
||||
VERSION ?= $(shell git describe --tags --always --dirty)
|
||||
IMAGE ?= registry-write.opensource.zalan.do/teapot/$(BINARY)
|
||||
TAG ?= $(VERSION)
|
||||
SOURCES = $(shell find . -name '*.go')
|
||||
DOCKERFILE ?= Dockerfile
|
||||
GOPKGS = $(shell go list ./...)
|
||||
GO_OPENAPI_GEN = ./build/openapi-gen
|
||||
OPENAPI_GEN = pkg/apiserver/generated/openapi/zz_generated.openapi.go
|
||||
BUILD_FLAGS ?= -v
|
||||
LDFLAGS ?= -X main.version=$(VERSION) -w -s
|
||||
BINARY ?= kube-metrics-adapter
|
||||
VERSION ?= $(shell git describe --tags --always --dirty)
|
||||
IMAGE ?= registry-write.opensource.zalan.do/teapot/$(BINARY)
|
||||
TAG ?= $(VERSION)
|
||||
SOURCES = $(shell find . -name '*.go')
|
||||
DOCKERFILE ?= Dockerfile
|
||||
GOPKGS = $(shell go list ./...)
|
||||
BUILD_FLAGS ?= -v
|
||||
OPENAPI ?= pkg/api/generated/openapi/zz_generated.openapi.go
|
||||
LDFLAGS ?= -X main.version=$(VERSION) -w -s
|
||||
|
||||
|
||||
default: build.local
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -rf $(OPENAPI_GEN)
|
||||
rm -rf $(OPENAPI)
|
||||
|
||||
test:
|
||||
go test -v $(GOPKGS)
|
||||
@ -25,24 +25,28 @@ check:
|
||||
go mod download
|
||||
golangci-lint run --timeout=2m ./...
|
||||
|
||||
$(GO_OPENAPI_GEN):
|
||||
mkdir -p build
|
||||
GOBIN=$(shell pwd)/build go install k8s.io/kube-openapi/cmd/openapi-gen
|
||||
|
||||
$(OPENAPI_GEN): $(GO_OPENAPI_GEN)
|
||||
$(GO_OPENAPI_GEN) -o . --go-header-file hack/boilerplate.go.txt --logtostderr -i k8s.io/metrics/pkg/apis/custom_metrics,k8s.io/metrics/pkg/apis/custom_metrics/v1beta1,k8s.io/metrics/pkg/apis/custom_metrics/v1beta2,k8s.io/metrics/pkg/apis/external_metrics,k8s.io/metrics/pkg/apis/external_metrics/v1beta1,k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/apimachinery/pkg/api/resource,k8s.io/apimachinery/pkg/version,k8s.io/api/core/v1 -p pkg/apiserver/generated/openapi -O zz_generated.openapi -r /dev/null
|
||||
$(OPENAPI): go.mod
|
||||
go run k8s.io/kube-openapi/cmd/openapi-gen \
|
||||
--go-header-file hack/boilerplate.go.txt \
|
||||
--logtostderr \
|
||||
-i k8s.io/metrics/pkg/apis/custom_metrics,k8s.io/metrics/pkg/apis/custom_metrics/v1beta1,k8s.io/metrics/pkg/apis/custom_metrics/v1beta2,k8s.io/metrics/pkg/apis/external_metrics,k8s.io/metrics/pkg/apis/external_metrics/v1beta1,k8s.io/metrics/pkg/apis/metrics,k8s.io/metrics/pkg/apis/metrics/v1beta1,k8s.io/apimachinery/pkg/apis/meta/v1,k8s.io/apimachinery/pkg/api/resource,k8s.io/apimachinery/pkg/version,k8s.io/api/core/v1 \
|
||||
-p pkg/api/generated/openapi \
|
||||
-o . \
|
||||
-O zz_generated.openapi \
|
||||
-r /dev/null
|
||||
|
||||
build.local: build/$(BINARY)
|
||||
build.linux: build/linux/$(BINARY)
|
||||
build.osx: build/osx/$(BINARY)
|
||||
|
||||
build/$(BINARY): go.mod $(OPENAPI_GEN) $(SOURCES)
|
||||
build/$(BINARY): go.mod $(SOURCES) $(OPENAPI)
|
||||
CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
|
||||
|
||||
build/linux/$(BINARY): go.mod $(OPENAPI_GEN) $(SOURCES)
|
||||
build/linux/$(BINARY): go.mod $(SOURCES) $(OPENAPI)
|
||||
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build $(BUILD_FLAGS) -o build/linux/$(BINARY) -ldflags "$(LDFLAGS)" .
|
||||
|
||||
build/osx/$(BINARY): go.mod $(OPENAPI_GEN) $(SOURCES)
|
||||
build/osx/$(BINARY): go.mod $(SOURCES) $(OPENAPI)
|
||||
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build $(BUILD_FLAGS) -o build/osx/$(BINARY) -ldflags "$(LDFLAGS)" .
|
||||
|
||||
build.docker: build.linux
|
||||
|
12
README.md
12
README.md
@ -142,7 +142,7 @@ example the following JSON data would be expected:
|
||||
```
|
||||
|
||||
The json-path query support depends on the
|
||||
[github.com/oliveagle/jsonpath](https://github.com/oliveagle/jsonpath) library.
|
||||
[github.com/spyzhov/ajson](https://github.com/spyzhov/ajson) library.
|
||||
See the README for possible queries. It's expected that the metric you query
|
||||
returns something that can be turned into a `float64`.
|
||||
|
||||
@ -202,6 +202,16 @@ the trade-offs between the two approaches.
|
||||
| `prometheus-query` | Generic metric which requires a user defined query. | External | | `>=1.12` |
|
||||
| *custom* | No predefined metrics. Metrics are generated from user defined queries. | Object | *any* | `>=1.12` |
|
||||
|
||||
### Configure Scrape Interval
|
||||
|
||||
There is a way to set the interval via an annotation like:
|
||||
|
||||
```yaml
|
||||
metric-config.external.prometheus-query.prometheus/interval: 30s
|
||||
```
|
||||
|
||||
Default is 1 minute.
|
||||
|
||||
### Example: External Metric
|
||||
|
||||
This is an example of an HPA configured to get metrics based on a Prometheus
|
||||
|
34
go.mod
34
go.mod
@ -2,34 +2,36 @@ module github.com/zalando-incubator/kube-metrics-adapter
|
||||
|
||||
require (
|
||||
github.com/NYTimes/gziphandler v1.0.1 // indirect
|
||||
github.com/aws/aws-sdk-go v1.31.7
|
||||
github.com/go-openapi/spec v0.19.3
|
||||
github.com/aws/aws-sdk-go v1.35.19
|
||||
github.com/go-openapi/spec v0.19.12
|
||||
github.com/googleapis/gnostic v0.2.0 // indirect
|
||||
github.com/influxdata/influxdb-client-go v0.1.5
|
||||
github.com/kubernetes-incubator/custom-metrics-apiserver v0.0.0-20200618121405-54026617ec44
|
||||
github.com/kubernetes-incubator/custom-metrics-apiserver v0.0.0-20201023074945-51cc7b53320e
|
||||
github.com/lib/pq v1.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852
|
||||
github.com/onsi/gomega v1.8.1 // indirect
|
||||
github.com/prometheus/client_golang v1.6.0
|
||||
github.com/prometheus/common v0.10.0
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/prometheus/client_golang v1.8.0
|
||||
github.com/prometheus/common v0.14.0
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/cobra v0.0.7
|
||||
github.com/stretchr/testify v1.6.0
|
||||
github.com/spyzhov/ajson v0.4.2
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/zalando-incubator/cluster-lifecycle-manager v0.0.0-20180921141935-824b77fb1f84
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
golang.org/x/tools v0.0.0-20200204192400-7124308813f3 // indirect
|
||||
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e // indirect
|
||||
honnef.co/go/tools v0.0.1-2020.1.3 // indirect
|
||||
k8s.io/api v0.18.4
|
||||
k8s.io/apiextensions-apiserver v0.18.4
|
||||
k8s.io/apimachinery v0.18.4
|
||||
k8s.io/apiserver v0.18.4
|
||||
k8s.io/client-go v0.18.4
|
||||
k8s.io/component-base v0.18.4
|
||||
k8s.io/api v0.18.8
|
||||
k8s.io/apimachinery v0.18.8
|
||||
k8s.io/apiserver v0.18.8
|
||||
k8s.io/client-go v0.18.8
|
||||
k8s.io/component-base v0.18.8
|
||||
k8s.io/klog v1.0.0
|
||||
k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6
|
||||
k8s.io/metrics v0.18.4
|
||||
k8s.io/metrics v0.18.8
|
||||
)
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright YEAR The Kubernetes Authors.
|
||||
Copyright 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.
|
||||
@ -13,3 +13,4 @@ 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.
|
||||
*/
|
||||
|
||||
|
6
hack/tools.go
Normal file
6
hack/tools.go
Normal file
@ -0,0 +1,6 @@
|
||||
// +build tools
|
||||
|
||||
// This package imports things required by build scripts, to force `go mod` to see them as dependencies
|
||||
package tools
|
||||
|
||||
import _ "k8s.io/kube-openapi/cmd/openapi-gen"
|
@ -1,7 +1,7 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2020 The Kubernetes Authors.
|
||||
Copyright 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.
|
||||
@ -293,6 +293,11 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"k8s.io/metrics/pkg/apis/custom_metrics/v1beta2.MetricValueList": schema_pkg_apis_custom_metrics_v1beta2_MetricValueList(ref),
|
||||
"k8s.io/metrics/pkg/apis/external_metrics/v1beta1.ExternalMetricValue": schema_pkg_apis_external_metrics_v1beta1_ExternalMetricValue(ref),
|
||||
"k8s.io/metrics/pkg/apis/external_metrics/v1beta1.ExternalMetricValueList": schema_pkg_apis_external_metrics_v1beta1_ExternalMetricValueList(ref),
|
||||
"k8s.io/metrics/pkg/apis/metrics/v1beta1.ContainerMetrics": schema_pkg_apis_metrics_v1beta1_ContainerMetrics(ref),
|
||||
"k8s.io/metrics/pkg/apis/metrics/v1beta1.NodeMetrics": schema_pkg_apis_metrics_v1beta1_NodeMetrics(ref),
|
||||
"k8s.io/metrics/pkg/apis/metrics/v1beta1.NodeMetricsList": schema_pkg_apis_metrics_v1beta1_NodeMetricsList(ref),
|
||||
"k8s.io/metrics/pkg/apis/metrics/v1beta1.PodMetrics": schema_pkg_apis_metrics_v1beta1_PodMetrics(ref),
|
||||
"k8s.io/metrics/pkg/apis/metrics/v1beta1.PodMetricsList": schema_pkg_apis_metrics_v1beta1_PodMetricsList(ref),
|
||||
}
|
||||
}
|
||||
|
||||
@ -13822,3 +13827,257 @@ func schema_pkg_apis_external_metrics_v1beta1_ExternalMetricValueList(ref common
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/metrics/pkg/apis/external_metrics/v1beta1.ExternalMetricValue"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_metrics_v1beta1_ContainerMetrics(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "ContainerMetrics sets resource usage metrics of a container.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Container name corresponding to the one from pod.spec.containers.",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"usage": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The memory usage is the memory working set.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "usage"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/api/resource.Quantity"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_metrics_v1beta1_NodeMetrics(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "NodeMetrics sets resource usage metrics of a node.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"timestamp": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The following fields define time interval from which metrics were collected from the interval [Timestamp-Window, Timestamp].",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
"window": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
|
||||
},
|
||||
},
|
||||
"usage": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The memory usage is the memory working set.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"timestamp", "window", "usage"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/api/resource.Quantity", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_metrics_v1beta1_NodeMetricsList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "NodeMetricsList is a list of NodeMetrics.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "List of node metrics.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/metrics/pkg/apis/metrics/v1beta1.NodeMetrics"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/metrics/pkg/apis/metrics/v1beta1.NodeMetrics"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_metrics_v1beta1_PodMetrics(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "PodMetrics sets resource usage metrics of a pod.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"timestamp": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The following fields define time interval from which metrics were collected from the interval [Timestamp-Window, Timestamp].",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"),
|
||||
},
|
||||
},
|
||||
"window": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
|
||||
},
|
||||
},
|
||||
"containers": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Metrics for all containers are collected within the same time window.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/metrics/pkg/apis/metrics/v1beta1.ContainerMetrics"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"timestamp", "window", "containers"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.Duration", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.Time", "k8s.io/metrics/pkg/apis/metrics/v1beta1.ContainerMetrics"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_metrics_v1beta1_PodMetricsList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "PodMetricsList is a list of PodMetrics.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "List of pod metrics.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Ref: ref("k8s.io/metrics/pkg/apis/metrics/v1beta1.PodMetrics"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/metrics/pkg/apis/metrics/v1beta1.PodMetrics"},
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/zalando-incubator/kube-metrics-adapter/pkg/collector/httpmetrics"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
"k8s.io/api/autoscaling/v2beta2"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -36,14 +35,12 @@ func (p *HTTPCollectorPlugin) NewCollector(_ *v2beta2.HorizontalPodAutoscaler, c
|
||||
if value, ok = config.Config[HTTPJsonPathAnnotationKey]; !ok {
|
||||
return nil, fmt.Errorf("config value %s not found", HTTPJsonPathAnnotationKey)
|
||||
}
|
||||
jsonPath, err := jsonpath.Compile(value)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse json path: %v", err)
|
||||
}
|
||||
collector.jsonPath = jsonPath
|
||||
jsonPath := value
|
||||
|
||||
if value, ok = config.Config[HTTPEndpointAnnotationKey]; !ok {
|
||||
return nil, fmt.Errorf("config value %s not found", HTTPEndpointAnnotationKey)
|
||||
}
|
||||
var err error
|
||||
collector.endpoint, err = url.Parse(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -65,13 +62,16 @@ func (p *HTTPCollectorPlugin) NewCollector(_ *v2beta2.HorizontalPodAutoscaler, c
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
collector.metricsGetter = httpmetrics.NewJSONPathMetricsGetter(httpmetrics.DefaultMetricsHTTPClient(), aggFunc, jsonPath)
|
||||
jsonPathGetter, err := httpmetrics.NewJSONPathMetricsGetter(httpmetrics.DefaultMetricsHTTPClient(), aggFunc, jsonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
collector.metricsGetter = jsonPathGetter
|
||||
return collector, nil
|
||||
}
|
||||
|
||||
type HTTPCollector struct {
|
||||
endpoint *url.URL
|
||||
jsonPath *jsonpath.Compiled
|
||||
interval time.Duration
|
||||
metricType v2beta2.MetricSourceType
|
||||
metricsGetter *httpmetrics.JSONPathMetricsGetter
|
||||
|
@ -1,7 +1,6 @@
|
||||
package httpmetrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@ -9,21 +8,26 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
"github.com/spyzhov/ajson"
|
||||
)
|
||||
|
||||
// JSONPathMetricsGetter is a metrics getter which looks up pod metrics by
|
||||
// querying the pods metrics endpoint and lookup the metric value as defined by
|
||||
// the json path query.
|
||||
type JSONPathMetricsGetter struct {
|
||||
jsonPath *jsonpath.Compiled
|
||||
jsonPath string
|
||||
aggregator AggregatorFunc
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewJSONPathMetricsGetter initializes a new JSONPathMetricsGetter.
|
||||
func NewJSONPathMetricsGetter(httpClient *http.Client, aggregatorFunc AggregatorFunc, compiledPath *jsonpath.Compiled) *JSONPathMetricsGetter {
|
||||
return &JSONPathMetricsGetter{client: httpClient, aggregator: aggregatorFunc, jsonPath: compiledPath}
|
||||
func NewJSONPathMetricsGetter(httpClient *http.Client, aggregatorFunc AggregatorFunc, jsonPath string) (*JSONPathMetricsGetter, error) {
|
||||
// check that jsonPath parses
|
||||
_, err := ajson.ParseJSONPath(jsonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &JSONPathMetricsGetter{client: httpClient, aggregator: aggregatorFunc, jsonPath: jsonPath}, nil
|
||||
}
|
||||
|
||||
var DefaultRequestTimeout = 15 * time.Second
|
||||
@ -58,57 +62,45 @@ func (g *JSONPathMetricsGetter) GetMetric(metricsURL url.URL) (float64, error) {
|
||||
}
|
||||
|
||||
// parse data
|
||||
var jsonData interface{}
|
||||
err = json.Unmarshal(data, &jsonData)
|
||||
root, err := ajson.Unmarshal(data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
res, err := g.jsonPath.Lookup(jsonData)
|
||||
nodes, err := root.JSONPath(g.jsonPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch res := res.(type) {
|
||||
case int:
|
||||
return float64(res), nil
|
||||
case float32:
|
||||
return float64(res), nil
|
||||
case float64:
|
||||
return res, nil
|
||||
case []interface{}:
|
||||
if len(nodes) != 1 {
|
||||
return 0, fmt.Errorf("unexpected json: expected single numeric or array value")
|
||||
}
|
||||
|
||||
node := nodes[0]
|
||||
if node.IsArray() {
|
||||
if g.aggregator == nil {
|
||||
return 0, fmt.Errorf("no aggregator function has been specified")
|
||||
}
|
||||
s, err := castSlice(res)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return g.aggregator(s...), nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unsupported type %T", res)
|
||||
}
|
||||
}
|
||||
|
||||
// castSlice takes a slice of interface and returns a slice of float64 if all
|
||||
// values in slice were castable, else returns an error
|
||||
func castSlice(in []interface{}) ([]float64, error) {
|
||||
var out []float64
|
||||
|
||||
for _, v := range in {
|
||||
switch v := v.(type) {
|
||||
case int:
|
||||
out = append(out, float64(v))
|
||||
case float32:
|
||||
out = append(out, float64(v))
|
||||
case float64:
|
||||
out = append(out, v)
|
||||
default:
|
||||
return nil, fmt.Errorf("slice was returned by JSONPath, but value inside is unsupported: %T", v)
|
||||
values := make([]float64, 0, len(nodes))
|
||||
items, _ := node.GetArray()
|
||||
for _, item := range items {
|
||||
value, err := item.GetNumeric()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("did not find numeric type: %w", err)
|
||||
}
|
||||
values = append(values, value)
|
||||
}
|
||||
return g.aggregator(values...), nil
|
||||
} else if node.IsNumeric() {
|
||||
res, _ := node.GetNumeric()
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return out, nil
|
||||
value, err := node.Value()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to check value of jsonPath result: %w", err)
|
||||
}
|
||||
return 0, fmt.Errorf("unsupported type %T", value)
|
||||
}
|
||||
|
||||
func (g *JSONPathMetricsGetter) fetchMetrics(metricsURL url.URL) ([]byte, error) {
|
||||
|
@ -1,59 +1,21 @@
|
||||
package httpmetrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCastSlice(t *testing.T) {
|
||||
res1, err1 := castSlice([]interface{}{1, 2, 3})
|
||||
require.NoError(t, err1)
|
||||
require.Equal(t, []float64{1.0, 2.0, 3.0}, res1)
|
||||
|
||||
res2, err2 := castSlice([]interface{}{float32(1.0), float32(2.0), float32(3.0)})
|
||||
require.NoError(t, err2)
|
||||
require.Equal(t, []float64{1.0, 2.0, 3.0}, res2)
|
||||
|
||||
res3, err3 := castSlice([]interface{}{float64(1.0), float64(2.0), float64(3.0)})
|
||||
require.NoError(t, err3)
|
||||
require.Equal(t, []float64{1.0, 2.0, 3.0}, res3)
|
||||
|
||||
res4, err4 := castSlice([]interface{}{1, 2, "some string"})
|
||||
require.Errorf(t, err4, "slice was returned by JSONPath, but value inside is unsupported: %T", "string")
|
||||
require.Equal(t, []float64(nil), res4)
|
||||
}
|
||||
|
||||
type testValueResponse struct {
|
||||
Value int64 `json:"value"`
|
||||
}
|
||||
|
||||
type testValueArrayResponse struct {
|
||||
Value []int64 `json:"value"`
|
||||
}
|
||||
|
||||
func makeTestHTTPServer(t *testing.T, values ...int64) *httptest.Server {
|
||||
func makeTestHTTPServer(t *testing.T, response []byte) *httptest.Server {
|
||||
h := func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, r.URL.Path, "/metrics")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var (
|
||||
response []byte
|
||||
err error
|
||||
)
|
||||
if len(values) == 1 {
|
||||
response, err = json.Marshal(testValueResponse{Value: values[0]})
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
response, err = json.Marshal(testValueArrayResponse{Value: values})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
_, err = w.Write(response)
|
||||
_, err := w.Write(response)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
return httptest.NewServer(http.HandlerFunc(h))
|
||||
@ -61,29 +23,62 @@ func makeTestHTTPServer(t *testing.T, values ...int64) *httptest.Server {
|
||||
|
||||
func TestJSONPathMetricsGetter(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
input []int64
|
||||
output float64
|
||||
aggregator AggregatorFunc
|
||||
name string
|
||||
jsonResponse []byte
|
||||
jsonPath string
|
||||
result float64
|
||||
aggregator AggregatorFunc
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "basic average",
|
||||
input: []int64{3, 4, 5},
|
||||
output: 4,
|
||||
aggregator: Average,
|
||||
name: "basic single value",
|
||||
jsonResponse: []byte(`{"value":3}`),
|
||||
jsonPath: "$.value",
|
||||
result: 3,
|
||||
aggregator: Average,
|
||||
},
|
||||
{
|
||||
name: "basic average",
|
||||
jsonResponse: []byte(`{"value":[3,4,5]}`),
|
||||
jsonPath: "$.value",
|
||||
result: 4,
|
||||
aggregator: Average,
|
||||
},
|
||||
{
|
||||
name: "dotted key",
|
||||
jsonResponse: []byte(`{"metric.value":5}`),
|
||||
jsonPath: "$['metric.value']",
|
||||
result: 5,
|
||||
aggregator: Average,
|
||||
},
|
||||
{
|
||||
name: "json path not resulting in array or number should lead to error",
|
||||
jsonResponse: []byte(`{"metric.value":5}`),
|
||||
jsonPath: "$['invalid.metric.values']",
|
||||
err: errors.New("unexpected json: expected single numeric or array value"),
|
||||
},
|
||||
{
|
||||
name: "invalid json should error",
|
||||
jsonResponse: []byte(`{`),
|
||||
jsonPath: "$['invalid.metric.values']",
|
||||
err: errors.New("unexpected end of file"),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
server := makeTestHTTPServer(t, tc.input...)
|
||||
server := makeTestHTTPServer(t, tc.jsonResponse)
|
||||
defer server.Close()
|
||||
path, err := jsonpath.Compile("$.value")
|
||||
getter, err := NewJSONPathMetricsGetter(DefaultMetricsHTTPClient(), tc.aggregator, tc.jsonPath)
|
||||
require.NoError(t, err)
|
||||
getter := NewJSONPathMetricsGetter(DefaultMetricsHTTPClient(), tc.aggregator, path)
|
||||
url, err := url.Parse(fmt.Sprintf("%s/metrics", server.URL))
|
||||
require.NoError(t, err)
|
||||
metric, err := getter.GetMetric(*url)
|
||||
if tc.err != nil {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tc.err.Error(), err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.output, metric)
|
||||
require.Equal(t, tc.result, metric)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
@ -33,18 +32,13 @@ func (g PodMetricsJSONPathGetter) GetMetric(pod *v1.Pod) (float64, error) {
|
||||
func NewPodMetricsJSONPathGetter(config map[string]string) (*PodMetricsJSONPathGetter, error) {
|
||||
getter := PodMetricsJSONPathGetter{}
|
||||
var (
|
||||
jsonPath *jsonpath.Compiled
|
||||
jsonPath string
|
||||
aggregator AggregatorFunc
|
||||
err error
|
||||
)
|
||||
|
||||
if v, ok := config["json-key"]; ok {
|
||||
path, err := jsonpath.Compile(v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse json path definition: %v", err)
|
||||
}
|
||||
|
||||
jsonPath = path
|
||||
jsonPath = v
|
||||
}
|
||||
|
||||
if v, ok := config["scheme"]; ok {
|
||||
@ -99,7 +93,11 @@ func NewPodMetricsJSONPathGetter(config map[string]string) (*PodMetricsJSONPathG
|
||||
connectTimeout = d
|
||||
}
|
||||
|
||||
getter.metricGetter = NewJSONPathMetricsGetter(CustomMetricsHTTPClient(requestTimeout, connectTimeout), aggregator, jsonPath)
|
||||
jsonPathGetter, err := NewJSONPathMetricsGetter(CustomMetricsHTTPClient(requestTimeout, connectTimeout), aggregator, jsonPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
getter.metricGetter = jsonPathGetter
|
||||
return &getter, nil
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oliveagle/jsonpath"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -23,12 +22,11 @@ func TestNewPodJSONPathMetricsGetter(t *testing.T) {
|
||||
"path": "/metrics",
|
||||
"port": "9090",
|
||||
}
|
||||
jpath1, _ := jsonpath.Compile(configNoAggregator["json-key"])
|
||||
getterNoAggregator, err1 := NewPodMetricsJSONPathGetter(configNoAggregator)
|
||||
|
||||
require.NoError(t, err1)
|
||||
compareMetricsGetter(t, &PodMetricsJSONPathGetter{
|
||||
metricGetter: &JSONPathMetricsGetter{jsonPath: jpath1},
|
||||
metricGetter: &JSONPathMetricsGetter{jsonPath: configNoAggregator["json-key"]},
|
||||
scheme: "http",
|
||||
path: "/metrics",
|
||||
port: 9090,
|
||||
@ -41,12 +39,11 @@ func TestNewPodJSONPathMetricsGetter(t *testing.T) {
|
||||
"port": "9090",
|
||||
"aggregator": "avg",
|
||||
}
|
||||
jpath2, _ := jsonpath.Compile(configAggregator["json-key"])
|
||||
getterAggregator, err2 := NewPodMetricsJSONPathGetter(configAggregator)
|
||||
|
||||
require.NoError(t, err2)
|
||||
compareMetricsGetter(t, &PodMetricsJSONPathGetter{
|
||||
metricGetter: &JSONPathMetricsGetter{jsonPath: jpath2, aggregator: Average},
|
||||
metricGetter: &JSONPathMetricsGetter{jsonPath: configAggregator["json-key"], aggregator: Average},
|
||||
scheme: "http",
|
||||
path: "/metrics",
|
||||
port: 9090,
|
||||
@ -79,12 +76,11 @@ func TestNewPodJSONPathMetricsGetter(t *testing.T) {
|
||||
"port": "9090",
|
||||
"raw-query": "foo=bar&baz=bop",
|
||||
}
|
||||
jpath5, _ := jsonpath.Compile(configWithRawQuery["json-key"])
|
||||
getterWithRawQuery, err5 := NewPodMetricsJSONPathGetter(configWithRawQuery)
|
||||
|
||||
require.NoError(t, err5)
|
||||
compareMetricsGetter(t, &PodMetricsJSONPathGetter{
|
||||
metricGetter: &JSONPathMetricsGetter{jsonPath: jpath5},
|
||||
metricGetter: &JSONPathMetricsGetter{jsonPath: configWithRawQuery["json-key"]},
|
||||
scheme: "http",
|
||||
path: "/metrics",
|
||||
port: 9090,
|
||||
@ -107,8 +103,6 @@ func TestBuildMetricsURL(t *testing.T) {
|
||||
"port": port,
|
||||
"raw-query": rawQuery,
|
||||
}
|
||||
_, err := jsonpath.Compile(configWithRawQuery["json-key"])
|
||||
require.NoError(t, err)
|
||||
getterWithRawQuery, err1 := NewPodMetricsJSONPathGetter(configWithRawQuery)
|
||||
require.NoError(t, err1)
|
||||
|
||||
@ -123,8 +117,6 @@ func TestBuildMetricsURL(t *testing.T) {
|
||||
"path": path,
|
||||
"port": port,
|
||||
}
|
||||
_, err2 := jsonpath.Compile(configWithNoQuery["json-key"])
|
||||
require.NoError(t, err2)
|
||||
getterWithNoQuery, err3 := NewPodMetricsJSONPathGetter(configWithNoQuery)
|
||||
require.NoError(t, err3)
|
||||
|
||||
@ -140,10 +132,10 @@ func TestCustomTimeouts(t *testing.T) {
|
||||
|
||||
// Test no custom options results in default timeouts
|
||||
defaultConfig := map[string]string{
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
}
|
||||
defaultTime := time.Duration(15000) * time.Millisecond
|
||||
|
||||
@ -153,10 +145,10 @@ func TestCustomTimeouts(t *testing.T) {
|
||||
|
||||
// Test with custom request timeout
|
||||
configWithRequestTimeout := map[string]string{
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"request-timeout": "978ms",
|
||||
}
|
||||
exectedTimeout := time.Duration(978) * time.Millisecond
|
||||
@ -167,30 +159,30 @@ func TestCustomTimeouts(t *testing.T) {
|
||||
// Test with custom connect timeout. Unfortunately, it seems there's no way to access the
|
||||
// connect timeout of the client struct to actually verify it's set :/
|
||||
configWithConnectTimeout := map[string]string{
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"connect-timeout": "512ms",
|
||||
}
|
||||
customRequestGetter, err3 := NewPodMetricsJSONPathGetter(configWithConnectTimeout)
|
||||
_, err3 := NewPodMetricsJSONPathGetter(configWithConnectTimeout)
|
||||
require.NoError(t, err3)
|
||||
|
||||
configWithInvalidTimeout := map[string]string{
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"request-timeout": "-256ms",
|
||||
}
|
||||
_, err4 := NewPodMetricsJSONPathGetter(configWithInvalidTimeout)
|
||||
require.Error(t, err4)
|
||||
|
||||
configWithInvalidTimeout = map[string]string{
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"json-key": "$.value",
|
||||
"scheme": scheme,
|
||||
"path": path,
|
||||
"port": port,
|
||||
"connect-timeout": "-256ms",
|
||||
}
|
||||
_, err5 := NewPodMetricsJSONPathGetter(configWithInvalidTimeout)
|
||||
|
@ -88,16 +88,16 @@ func NewSkipperCollector(client kubernetes.Interface, plugin CollectorPlugin, hp
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getAnnotationWeight(backendWeights string, backend string) float64 {
|
||||
var weightsMap map[string]int
|
||||
func getAnnotationWeight(backendWeights string, backend string) (float64, error) {
|
||||
var weightsMap map[string]float64
|
||||
err := json.Unmarshal([]byte(backendWeights), &weightsMap)
|
||||
if err != nil {
|
||||
return 0
|
||||
return 0, err
|
||||
}
|
||||
if weight, ok := weightsMap[backend]; ok {
|
||||
return float64(weight) / 100
|
||||
return float64(weight) / 100, nil
|
||||
}
|
||||
return 0
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func getWeights(ingressAnnotations map[string]string, backendAnnotations []string, backend string) (float64, error) {
|
||||
@ -107,7 +107,11 @@ func getWeights(ingressAnnotations map[string]string, backendAnnotations []strin
|
||||
for _, anno := range backendAnnotations {
|
||||
if weightsMap, ok := ingressAnnotations[anno]; ok {
|
||||
annotationsPresent = true
|
||||
maxWeight = math.Max(maxWeight, getAnnotationWeight(weightsMap, backend))
|
||||
weight, err := getAnnotationWeight(weightsMap, backend)
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
maxWeight = math.Max(maxWeight, weight)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,7 +112,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
expectError bool
|
||||
fakedAverage bool
|
||||
namespace string
|
||||
backendWeights map[string]map[string]int
|
||||
backendWeights map[string]map[string]float64
|
||||
replicas int32
|
||||
readyReplicas int32
|
||||
backendAnnotations []string
|
||||
@ -138,7 +138,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 1000,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 60, "backend1": 40}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 60.0, "backend1": 40}},
|
||||
replicas: 1,
|
||||
readyReplicas: 1,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -152,7 +152,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 1000,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 60, "backend1": 40}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 60, "backend1": 40}},
|
||||
replicas: 1,
|
||||
readyReplicas: 1,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -167,7 +167,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
fakedAverage: true,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 50, "backend1": 50}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 50, "backend1": 50}},
|
||||
replicas: 5,
|
||||
readyReplicas: 5,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -181,7 +181,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 1500,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 50, "backend1": 50}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 50, "backend1": 50}},
|
||||
replicas: 5, // this is not taken into account
|
||||
readyReplicas: 5,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -195,7 +195,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 0,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 100, "backend1": 0}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 100, "backend1": 0}},
|
||||
replicas: 5,
|
||||
readyReplicas: 5,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -210,7 +210,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
fakedAverage: true,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{
|
||||
backendWeights: map[string]map[string]float64{
|
||||
testBackendWeightsAnnotation: {"backend2": 20, "backend1": 80},
|
||||
testStacksetWeightsAnnotation: {"backend2": 0, "backend1": 100},
|
||||
},
|
||||
@ -227,7 +227,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 1500,
|
||||
namespace: "default",
|
||||
backend: "backend1",
|
||||
backendWeights: map[string]map[string]int{
|
||||
backendWeights: map[string]map[string]float64{
|
||||
testBackendWeightsAnnotation: {"backend2": 20, "backend1": 80},
|
||||
testStacksetWeightsAnnotation: {"backend2": 0, "backend1": 100},
|
||||
},
|
||||
@ -244,7 +244,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 0,
|
||||
namespace: "default",
|
||||
backend: "backend3",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 100, "backend1": 0}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 100, "backend1": 0}},
|
||||
replicas: 1,
|
||||
readyReplicas: 1,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -258,7 +258,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
collectedMetric: 1500,
|
||||
namespace: "default",
|
||||
backend: "backend3",
|
||||
backendWeights: map[string]map[string]int{},
|
||||
backendWeights: map[string]map[string]float64{},
|
||||
replicas: 1,
|
||||
readyReplicas: 1,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -272,7 +272,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
expectError: true,
|
||||
namespace: "default",
|
||||
backend: "",
|
||||
backendWeights: map[string]map[string]int{testBackendWeightsAnnotation: {"backend2": 100, "backend1": 0}},
|
||||
backendWeights: map[string]map[string]float64{testBackendWeightsAnnotation: {"backend2": 100, "backend1": 0}},
|
||||
replicas: 1,
|
||||
readyReplicas: 1,
|
||||
backendAnnotations: []string{testBackendWeightsAnnotation},
|
||||
@ -301,7 +301,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
fakedAverage: true,
|
||||
namespace: "default",
|
||||
backend: "backend2",
|
||||
backendWeights: map[string]map[string]int{
|
||||
backendWeights: map[string]map[string]float64{
|
||||
testBackendWeightsAnnotation: {"backend2": 20, "backend1": 80},
|
||||
testStacksetWeightsAnnotation: {"backend1": 100},
|
||||
},
|
||||
@ -314,12 +314,12 @@ func TestSkipperCollector(t *testing.T) {
|
||||
metric: 1500,
|
||||
ingressName: "dummy-ingress",
|
||||
hostnames: []string{"example.org"},
|
||||
expectedQuery: `scalar(sum(rate(skipper_serve_host_duration_seconds_count{host=~"example_org"}[1m])) * 0.2000)`,
|
||||
expectedQuery: `scalar(sum(rate(skipper_serve_host_duration_seconds_count{host=~"example_org"}[1m])) * 0.2050)`,
|
||||
collectedMetric: 1500,
|
||||
namespace: "default",
|
||||
backend: "backend2",
|
||||
backendWeights: map[string]map[string]int{
|
||||
testBackendWeightsAnnotation: {"backend2": 20, "backend1": 80},
|
||||
backendWeights: map[string]map[string]float64{
|
||||
testBackendWeightsAnnotation: {"backend2": 20.5, "backend1": 79.5},
|
||||
testStacksetWeightsAnnotation: {"backend1": 100},
|
||||
},
|
||||
replicas: 5,
|
||||
@ -352,7 +352,7 @@ func TestSkipperCollector(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeIngress(client kubernetes.Interface, namespace, ingressName, backend string, hostnames []string, backendWeights map[string]map[string]int) error {
|
||||
func makeIngress(client kubernetes.Interface, namespace, ingressName, backend string, hostnames []string, backendWeights map[string]map[string]float64) error {
|
||||
annotations := make(map[string]string)
|
||||
for anno, weights := range backendWeights {
|
||||
sWeights, err := json.Marshal(weights)
|
||||
|
@ -25,16 +25,16 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/apiserver"
|
||||
"github.com/kubernetes-incubator/custom-metrics-apiserver/pkg/cmd/server"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/zalando-incubator/cluster-lifecycle-manager/pkg/credentials-loader/platformiam"
|
||||
generatedopenapi "github.com/zalando-incubator/kube-metrics-adapter/pkg/apiserver/generated/openapi"
|
||||
generatedopenapi "github.com/zalando-incubator/kube-metrics-adapter/pkg/api/generated/openapi"
|
||||
"github.com/zalando-incubator/kube-metrics-adapter/pkg/collector"
|
||||
"github.com/zalando-incubator/kube-metrics-adapter/pkg/provider"
|
||||
"github.com/zalando-incubator/kube-metrics-adapter/pkg/zmon"
|
||||
"golang.org/x/oauth2"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver"
|
||||
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/client-go/informers"
|
||||
|
Reference in New Issue
Block a user