mirror of https://github.com/k3s-io/k3s
Merge pull request #58784 from wackxu/reminit
Automatic merge from submit-queue (batch tested with PRs 58784, 62057, 62621, 62652, 62656). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. remove deprecated initresource admission plugin **What this PR does / why we need it**: **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: xref https://github.com/kubernetes/kubernetes/pull/55375#issuecomment-360329586 **Special notes for your reviewer**: /assign @piosz @deads2k **Release note**: ```release-note remove deprecated initresource admission plugin ```pull/8/head
commit
229ab73ada
|
@ -398,7 +398,6 @@ pkg/volume/vsphere_volume
|
||||||
plugin/pkg/admission/antiaffinity
|
plugin/pkg/admission/antiaffinity
|
||||||
plugin/pkg/admission/eventratelimit/apis/eventratelimit
|
plugin/pkg/admission/eventratelimit/apis/eventratelimit
|
||||||
plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1
|
plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1
|
||||||
plugin/pkg/admission/initialresources
|
|
||||||
plugin/pkg/admission/limitranger
|
plugin/pkg/admission/limitranger
|
||||||
plugin/pkg/admission/noderestriction
|
plugin/pkg/admission/noderestriction
|
||||||
plugin/pkg/admission/podnodeselector
|
plugin/pkg/admission/podnodeselector
|
||||||
|
|
|
@ -37,7 +37,6 @@ go_library(
|
||||||
"//plugin/pkg/admission/extendedresourcetoleration:go_default_library",
|
"//plugin/pkg/admission/extendedresourcetoleration:go_default_library",
|
||||||
"//plugin/pkg/admission/gc:go_default_library",
|
"//plugin/pkg/admission/gc:go_default_library",
|
||||||
"//plugin/pkg/admission/imagepolicy:go_default_library",
|
"//plugin/pkg/admission/imagepolicy:go_default_library",
|
||||||
"//plugin/pkg/admission/initialresources:go_default_library",
|
|
||||||
"//plugin/pkg/admission/limitranger:go_default_library",
|
"//plugin/pkg/admission/limitranger:go_default_library",
|
||||||
"//plugin/pkg/admission/namespace/autoprovision:go_default_library",
|
"//plugin/pkg/admission/namespace/autoprovision:go_default_library",
|
||||||
"//plugin/pkg/admission/namespace/exists:go_default_library",
|
"//plugin/pkg/admission/namespace/exists:go_default_library",
|
||||||
|
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration"
|
"k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/gc"
|
"k8s.io/kubernetes/plugin/pkg/admission/gc"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
"k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/initialresources"
|
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/limitranger"
|
"k8s.io/kubernetes/plugin/pkg/admission/limitranger"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision"
|
"k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/namespace/exists"
|
"k8s.io/kubernetes/plugin/pkg/admission/namespace/exists"
|
||||||
|
@ -68,7 +67,6 @@ var AllOrderedPlugins = []string{
|
||||||
exists.PluginName, // NamespaceExists
|
exists.PluginName, // NamespaceExists
|
||||||
scdeny.PluginName, // SecurityContextDeny
|
scdeny.PluginName, // SecurityContextDeny
|
||||||
antiaffinity.PluginName, // LimitPodHardAntiAffinityTopology
|
antiaffinity.PluginName, // LimitPodHardAntiAffinityTopology
|
||||||
initialresources.PluginName, // InitialResources
|
|
||||||
podpreset.PluginName, // PodPreset
|
podpreset.PluginName, // PodPreset
|
||||||
limitranger.PluginName, // LimitRanger
|
limitranger.PluginName, // LimitRanger
|
||||||
serviceaccount.PluginName, // ServiceAccount
|
serviceaccount.PluginName, // ServiceAccount
|
||||||
|
@ -109,7 +107,6 @@ func RegisterAllAdmissionPlugins(plugins *admission.Plugins) {
|
||||||
extendedresourcetoleration.Register(plugins)
|
extendedresourcetoleration.Register(plugins)
|
||||||
gc.Register(plugins)
|
gc.Register(plugins)
|
||||||
imagepolicy.Register(plugins)
|
imagepolicy.Register(plugins)
|
||||||
initialresources.Register(plugins)
|
|
||||||
limitranger.Register(plugins)
|
limitranger.Register(plugins)
|
||||||
autoprovision.Register(plugins)
|
autoprovision.Register(plugins)
|
||||||
exists.Register(plugins)
|
exists.Register(plugins)
|
||||||
|
|
|
@ -21,7 +21,6 @@ filegroup(
|
||||||
"//plugin/pkg/admission/extendedresourcetoleration:all-srcs",
|
"//plugin/pkg/admission/extendedresourcetoleration:all-srcs",
|
||||||
"//plugin/pkg/admission/gc:all-srcs",
|
"//plugin/pkg/admission/gc:all-srcs",
|
||||||
"//plugin/pkg/admission/imagepolicy:all-srcs",
|
"//plugin/pkg/admission/imagepolicy:all-srcs",
|
||||||
"//plugin/pkg/admission/initialresources:all-srcs",
|
|
||||||
"//plugin/pkg/admission/limitranger:all-srcs",
|
"//plugin/pkg/admission/limitranger:all-srcs",
|
||||||
"//plugin/pkg/admission/namespace/autoprovision:all-srcs",
|
"//plugin/pkg/admission/namespace/autoprovision:all-srcs",
|
||||||
"//plugin/pkg/admission/namespace/exists:all-srcs",
|
"//plugin/pkg/admission/namespace/exists:all-srcs",
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = [
|
|
||||||
"admission.go",
|
|
||||||
"data_source.go",
|
|
||||||
"gcm.go",
|
|
||||||
"hawkular.go",
|
|
||||||
"influxdb.go",
|
|
||||||
],
|
|
||||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/initialresources",
|
|
||||||
deps = [
|
|
||||||
"//pkg/apis/core:go_default_library",
|
|
||||||
"//vendor/cloud.google.com/go/compute/metadata:go_default_library",
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
|
||||||
"//vendor/github.com/hawkular/hawkular-client-go/metrics:go_default_library",
|
|
||||||
"//vendor/github.com/influxdata/influxdb/client:go_default_library",
|
|
||||||
"//vendor/golang.org/x/oauth2:go_default_library",
|
|
||||||
"//vendor/golang.org/x/oauth2/google:go_default_library",
|
|
||||||
"//vendor/google.golang.org/api/cloudmonitoring/v2beta2:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
|
||||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
|
||||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = [
|
|
||||||
"admission_test.go",
|
|
||||||
"data_source_test.go",
|
|
||||||
"gcm_test.go",
|
|
||||||
"hawkular_test.go",
|
|
||||||
"influxdb_test.go",
|
|
||||||
],
|
|
||||||
embed = [":go_default_library"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/apis/core:go_default_library",
|
|
||||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
|
||||||
"//vendor/golang.org/x/oauth2:go_default_library",
|
|
||||||
"//vendor/golang.org/x/oauth2/google:go_default_library",
|
|
||||||
"//vendor/google.golang.org/api/cloudmonitoring/v2beta2:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
|
||||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
|
@ -1,220 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
"k8s.io/apiserver/pkg/admission"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
source = flag.String("ir-data-source", "influxdb", "Data source used by InitialResources. Supported options: influxdb, gcm.")
|
|
||||||
percentile = flag.Int64("ir-percentile", 90, "Which percentile of samples should InitialResources use when estimating resources. For experiment purposes.")
|
|
||||||
nsOnly = flag.Bool("ir-namespace-only", false, "Whether the estimation should be made only based on data from the same namespace.")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
initialResourcesAnnotation = "kubernetes.io/initial-resources"
|
|
||||||
samplesThreshold = 30
|
|
||||||
week = 7 * 24 * time.Hour
|
|
||||||
month = 30 * 24 * time.Hour
|
|
||||||
// PluginName indicates name of admission plugin.
|
|
||||||
PluginName = "InitialResources"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register registers a plugin
|
|
||||||
// WARNING: this feature is experimental and will definitely change.
|
|
||||||
func Register(plugins *admission.Plugins) {
|
|
||||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
|
||||||
// TODO: remove the usage of flags in favor of reading versioned configuration
|
|
||||||
s, err := newDataSource(*source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newInitialResources(s, *percentile, *nsOnly), nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type InitialResources struct {
|
|
||||||
*admission.Handler
|
|
||||||
source dataSource
|
|
||||||
percentile int64
|
|
||||||
nsOnly bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ admission.MutationInterface = &InitialResources{}
|
|
||||||
|
|
||||||
func newInitialResources(source dataSource, percentile int64, nsOnly bool) *InitialResources {
|
|
||||||
return &InitialResources{
|
|
||||||
Handler: admission.NewHandler(admission.Create),
|
|
||||||
source: source,
|
|
||||||
percentile: percentile,
|
|
||||||
nsOnly: nsOnly,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Admit makes an admission decision based on the request attributes
|
|
||||||
func (ir InitialResources) Admit(a admission.Attributes) (err error) {
|
|
||||||
// Ignore all calls to subresources or resources other than pods.
|
|
||||||
if a.GetSubresource() != "" || a.GetResource().GroupResource() != api.Resource("pods") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
pod, ok := a.GetObject().(*api.Pod)
|
|
||||||
if !ok {
|
|
||||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
|
||||||
}
|
|
||||||
|
|
||||||
ir.estimateAndFillResourcesIfNotSet(pod)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The method veryfies whether resources should be set for the given pod and
|
|
||||||
// if there is estimation available the method fills Request field.
|
|
||||||
func (ir InitialResources) estimateAndFillResourcesIfNotSet(pod *api.Pod) {
|
|
||||||
var annotations []string
|
|
||||||
for i := range pod.Spec.InitContainers {
|
|
||||||
annotations = append(annotations, ir.estimateContainer(pod, &pod.Spec.InitContainers[i], "init container")...)
|
|
||||||
}
|
|
||||||
for i := range pod.Spec.Containers {
|
|
||||||
annotations = append(annotations, ir.estimateContainer(pod, &pod.Spec.Containers[i], "container")...)
|
|
||||||
}
|
|
||||||
if len(annotations) > 0 {
|
|
||||||
if pod.ObjectMeta.Annotations == nil {
|
|
||||||
pod.ObjectMeta.Annotations = make(map[string]string)
|
|
||||||
}
|
|
||||||
val := "Initial Resources plugin set: " + strings.Join(annotations, "; ")
|
|
||||||
pod.ObjectMeta.Annotations[initialResourcesAnnotation] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ir InitialResources) estimateContainer(pod *api.Pod, c *api.Container, message string) []string {
|
|
||||||
var annotations []string
|
|
||||||
req := c.Resources.Requests
|
|
||||||
cpu := ir.getEstimationIfNeeded(api.ResourceCPU, c, pod.ObjectMeta.Namespace)
|
|
||||||
mem := ir.getEstimationIfNeeded(api.ResourceMemory, c, pod.ObjectMeta.Namespace)
|
|
||||||
// If Requests doesn't exits and an estimation was made, create Requests.
|
|
||||||
if req == nil && (cpu != nil || mem != nil) {
|
|
||||||
c.Resources.Requests = api.ResourceList{}
|
|
||||||
req = c.Resources.Requests
|
|
||||||
}
|
|
||||||
setRes := []string{}
|
|
||||||
if cpu != nil {
|
|
||||||
glog.Infof("CPU estimation for %s %v in pod %v/%v is %v", message, c.Name, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, cpu.String())
|
|
||||||
setRes = append(setRes, string(api.ResourceCPU))
|
|
||||||
req[api.ResourceCPU] = *cpu
|
|
||||||
}
|
|
||||||
if mem != nil {
|
|
||||||
glog.Infof("Memory estimation for %s %v in pod %v/%v is %v", message, c.Name, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name, mem.String())
|
|
||||||
setRes = append(setRes, string(api.ResourceMemory))
|
|
||||||
req[api.ResourceMemory] = *mem
|
|
||||||
}
|
|
||||||
if len(setRes) > 0 {
|
|
||||||
sort.Strings(setRes)
|
|
||||||
a := strings.Join(setRes, ", ") + fmt.Sprintf(" request for %s %s", message, c.Name)
|
|
||||||
annotations = append(annotations, a)
|
|
||||||
}
|
|
||||||
return annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
// getEstimationIfNeeded estimates compute resource for container if its corresponding
|
|
||||||
// Request(min amount) and Limit(max amount) both are not specified.
|
|
||||||
func (ir InitialResources) getEstimationIfNeeded(kind api.ResourceName, c *api.Container, ns string) *resource.Quantity {
|
|
||||||
requests := c.Resources.Requests
|
|
||||||
limits := c.Resources.Limits
|
|
||||||
var quantity *resource.Quantity
|
|
||||||
var err error
|
|
||||||
if _, requestFound := requests[kind]; !requestFound {
|
|
||||||
if _, limitFound := limits[kind]; !limitFound {
|
|
||||||
quantity, err = ir.getEstimation(kind, c, ns)
|
|
||||||
if err != nil {
|
|
||||||
glog.Errorf("Error while trying to estimate resources: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return quantity
|
|
||||||
}
|
|
||||||
func (ir InitialResources) getEstimation(kind api.ResourceName, c *api.Container, ns string) (*resource.Quantity, error) {
|
|
||||||
end := time.Now()
|
|
||||||
start := end.Add(-week)
|
|
||||||
var usage, samples int64
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// Historical data from last 7 days for the same image:tag within the same namespace.
|
|
||||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, ns, true, start, end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if samples < samplesThreshold {
|
|
||||||
// Historical data from last 30 days for the same image:tag within the same namespace.
|
|
||||||
start := end.Add(-month)
|
|
||||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, ns, true, start, end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are allowed to estimate only based on data from the same namespace.
|
|
||||||
if ir.nsOnly {
|
|
||||||
if samples < samplesThreshold {
|
|
||||||
// Historical data from last 30 days for the same image within the same namespace.
|
|
||||||
start := end.Add(-month)
|
|
||||||
image := strings.Split(c.Image, ":")[0]
|
|
||||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, image, ns, false, start, end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if samples < samplesThreshold {
|
|
||||||
// Historical data from last 7 days for the same image:tag within all namespaces.
|
|
||||||
start := end.Add(-week)
|
|
||||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, "", true, start, end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if samples < samplesThreshold {
|
|
||||||
// Historical data from last 30 days for the same image:tag within all namespaces.
|
|
||||||
start := end.Add(-month)
|
|
||||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, c.Image, "", true, start, end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if samples < samplesThreshold {
|
|
||||||
// Historical data from last 30 days for the same image within all namespaces.
|
|
||||||
start := end.Add(-month)
|
|
||||||
image := strings.Split(c.Image, ":")[0]
|
|
||||||
if usage, samples, err = ir.source.GetUsagePercentile(kind, ir.percentile, image, "", false, start, end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if samples > 0 && kind == api.ResourceCPU {
|
|
||||||
return resource.NewMilliQuantity(usage, resource.DecimalSI), nil
|
|
||||||
}
|
|
||||||
if samples > 0 && kind == api.ResourceMemory {
|
|
||||||
return resource.NewQuantity(usage, resource.DecimalSI), nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
|
@ -1,300 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/resource"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apiserver/pkg/admission"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeSource struct {
|
|
||||||
f func(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *fakeSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (usage int64, samples int64, err error) {
|
|
||||||
return s.f(kind, perc, image, namespace, exactMatch, start, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseReq(cpu, mem string) api.ResourceList {
|
|
||||||
if cpu == "" && mem == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
req := api.ResourceList{}
|
|
||||||
if cpu != "" {
|
|
||||||
req[api.ResourceCPU] = resource.MustParse(cpu)
|
|
||||||
}
|
|
||||||
if mem != "" {
|
|
||||||
req[api.ResourceMemory] = resource.MustParse(mem)
|
|
||||||
}
|
|
||||||
return req
|
|
||||||
}
|
|
||||||
|
|
||||||
func addContainer(pod *api.Pod, name, image string, request api.ResourceList) {
|
|
||||||
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
|
|
||||||
Name: name,
|
|
||||||
Image: image,
|
|
||||||
Resources: api.ResourceRequirements{Requests: request},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPod(name string, image string, request api.ResourceList) *api.Pod {
|
|
||||||
pod := &api.Pod{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test-ns"},
|
|
||||||
Spec: api.PodSpec{},
|
|
||||||
}
|
|
||||||
pod.Spec.Containers = []api.Container{}
|
|
||||||
addContainer(pod, "i0", image, request)
|
|
||||||
pod.Spec.InitContainers = pod.Spec.Containers
|
|
||||||
pod.Spec.Containers = []api.Container{}
|
|
||||||
addContainer(pod, "c0", image, request)
|
|
||||||
return pod
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPods() []*api.Pod {
|
|
||||||
return []*api.Pod{
|
|
||||||
createPod("p0", "image:v0", parseReq("", "")),
|
|
||||||
createPod("p1", "image:v1", parseReq("", "300")),
|
|
||||||
createPod("p2", "image:v2", parseReq("300m", "")),
|
|
||||||
createPod("p3", "image:v3", parseReq("300m", "300")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyContainer(t *testing.T, c *api.Container, cpu, mem int64) {
|
|
||||||
req := c.Resources.Requests
|
|
||||||
if req.Cpu().MilliValue() != cpu {
|
|
||||||
t.Errorf("Wrong CPU request for container %v. Expected %v, got %v.", c.Name, cpu, req.Cpu().MilliValue())
|
|
||||||
}
|
|
||||||
if req.Memory().Value() != mem {
|
|
||||||
t.Errorf("Wrong memory request for container %v. Expected %v, got %v.", c.Name, mem, req.Memory().Value())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyPod(t *testing.T, pod *api.Pod, cpu, mem int64) {
|
|
||||||
verifyContainer(t, &pod.Spec.Containers[0], cpu, mem)
|
|
||||||
verifyContainer(t, &pod.Spec.InitContainers[0], cpu, mem)
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyAnnotation(t *testing.T, pod *api.Pod, expected string) {
|
|
||||||
a, ok := pod.ObjectMeta.Annotations[initialResourcesAnnotation]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("No annotation but expected %v", expected)
|
|
||||||
}
|
|
||||||
if a != expected {
|
|
||||||
t.Errorf("Wrong annotation set by Initial Resources: got %v, expected %v", a, expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func expectNoAnnotation(t *testing.T, pod *api.Pod) {
|
|
||||||
if a, ok := pod.ObjectMeta.Annotations[initialResourcesAnnotation]; ok {
|
|
||||||
t.Errorf("Expected no annotation but got %v", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func admit(t *testing.T, ir admission.MutationInterface, pods []*api.Pod) {
|
|
||||||
for i := range pods {
|
|
||||||
p := pods[i]
|
|
||||||
|
|
||||||
podKind := api.Kind("Pod").WithVersion("version")
|
|
||||||
podRes := api.Resource("pods").WithVersion("version")
|
|
||||||
attrs := admission.NewAttributesRecord(p, nil, podKind, "test", p.ObjectMeta.Name, podRes, "", admission.Create, nil)
|
|
||||||
if err := ir.Admit(attrs); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAdminScenarios(t *testing.T, ir admission.MutationInterface, p *api.Pod) {
|
|
||||||
podKind := api.Kind("Pod").WithVersion("version")
|
|
||||||
podRes := api.Resource("pods").WithVersion("version")
|
|
||||||
|
|
||||||
var tests = []struct {
|
|
||||||
attrs admission.Attributes
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
admission.NewAttributesRecord(p, nil, podKind, "test", p.ObjectMeta.Name, podRes, "foo", admission.Create, nil),
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
admission.NewAttributesRecord(&api.ReplicationController{}, nil, podKind, "test", "", podRes, "", admission.Create, nil),
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
err := ir.Admit(test.attrs)
|
|
||||||
if err != nil && test.expectError == false {
|
|
||||||
t.Error(err)
|
|
||||||
} else if err == nil && test.expectError == true {
|
|
||||||
t.Error("Error expected for Admit but received none")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func performTest(t *testing.T, ir admission.MutationInterface) {
|
|
||||||
pods := getPods()
|
|
||||||
admit(t, ir, pods)
|
|
||||||
testAdminScenarios(t, ir, pods[0])
|
|
||||||
|
|
||||||
verifyPod(t, pods[0], 100, 100)
|
|
||||||
verifyPod(t, pods[1], 100, 300)
|
|
||||||
verifyPod(t, pods[2], 300, 100)
|
|
||||||
verifyPod(t, pods[3], 300, 300)
|
|
||||||
|
|
||||||
verifyAnnotation(t, pods[0], "Initial Resources plugin set: cpu, memory request for init container i0; cpu, memory request for container c0")
|
|
||||||
verifyAnnotation(t, pods[1], "Initial Resources plugin set: cpu request for init container i0")
|
|
||||||
verifyAnnotation(t, pods[2], "Initial Resources plugin set: memory request for init container i0")
|
|
||||||
expectNoAnnotation(t, pods[3])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstimateReturnsErrorFromSource(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
return 0, 0, errors.New("Example error")
|
|
||||||
}
|
|
||||||
ir := newInitialResources(&fakeSource{f: f}, 90, false)
|
|
||||||
admit(t, ir, getPods())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstimationBasedOnTheSameImageSameNamespace7d(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
if exactMatch && end.Sub(start) == week && ns == "test-ns" {
|
|
||||||
return 100, 120, nil
|
|
||||||
}
|
|
||||||
return 200, 120, nil
|
|
||||||
}
|
|
||||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstimationBasedOnTheSameImageSameNamespace30d(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
if exactMatch && end.Sub(start) == week && ns == "test-ns" {
|
|
||||||
return 200, 20, nil
|
|
||||||
}
|
|
||||||
if exactMatch && end.Sub(start) == month && ns == "test-ns" {
|
|
||||||
return 100, 120, nil
|
|
||||||
}
|
|
||||||
return 200, 120, nil
|
|
||||||
}
|
|
||||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstimationBasedOnTheSameImageAllNamespaces7d(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
if exactMatch && ns == "test-ns" {
|
|
||||||
return 200, 20, nil
|
|
||||||
}
|
|
||||||
if exactMatch && end.Sub(start) == week && ns == "" {
|
|
||||||
return 100, 120, nil
|
|
||||||
}
|
|
||||||
return 200, 120, nil
|
|
||||||
}
|
|
||||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstimationBasedOnTheSameImageAllNamespaces30d(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
if exactMatch && ns == "test-ns" {
|
|
||||||
return 200, 20, nil
|
|
||||||
}
|
|
||||||
if exactMatch && end.Sub(start) == week && ns == "" {
|
|
||||||
return 200, 20, nil
|
|
||||||
}
|
|
||||||
if exactMatch && end.Sub(start) == month && ns == "" {
|
|
||||||
return 100, 120, nil
|
|
||||||
}
|
|
||||||
return 200, 120, nil
|
|
||||||
}
|
|
||||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstimationBasedOnOtherImages(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, image, ns string, exactMatch bool, _, _ time.Time) (int64, int64, error) {
|
|
||||||
if image == "image" && !exactMatch && ns == "" {
|
|
||||||
return 100, 5, nil
|
|
||||||
}
|
|
||||||
return 200, 20, nil
|
|
||||||
}
|
|
||||||
performTest(t, newInitialResources(&fakeSource{f: f}, 90, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoData(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, _ bool, _, _ time.Time) (int64, int64, error) {
|
|
||||||
return 200, 0, nil
|
|
||||||
}
|
|
||||||
ir := newInitialResources(&fakeSource{f: f}, 90, false)
|
|
||||||
|
|
||||||
pods := []*api.Pod{
|
|
||||||
createPod("p0", "image:v0", parseReq("", "")),
|
|
||||||
}
|
|
||||||
admit(t, ir, pods)
|
|
||||||
|
|
||||||
if pods[0].Spec.Containers[0].Resources.Requests != nil {
|
|
||||||
t.Errorf("Unexpected resource estimation")
|
|
||||||
}
|
|
||||||
|
|
||||||
expectNoAnnotation(t, pods[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestManyContainers(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, _, _ time.Time) (int64, int64, error) {
|
|
||||||
if exactMatch {
|
|
||||||
return 100, 120, nil
|
|
||||||
}
|
|
||||||
return 200, 30, nil
|
|
||||||
}
|
|
||||||
ir := newInitialResources(&fakeSource{f: f}, 90, false)
|
|
||||||
|
|
||||||
pod := createPod("p", "image:v0", parseReq("", ""))
|
|
||||||
addContainer(pod, "c1", "image:v1", parseReq("", "300"))
|
|
||||||
addContainer(pod, "c2", "image:v2", parseReq("300m", ""))
|
|
||||||
addContainer(pod, "c3", "image:v3", parseReq("300m", "300"))
|
|
||||||
admit(t, ir, []*api.Pod{pod})
|
|
||||||
|
|
||||||
verifyContainer(t, &pod.Spec.Containers[0], 100, 100)
|
|
||||||
verifyContainer(t, &pod.Spec.Containers[1], 100, 300)
|
|
||||||
verifyContainer(t, &pod.Spec.Containers[2], 300, 100)
|
|
||||||
verifyContainer(t, &pod.Spec.Containers[3], 300, 300)
|
|
||||||
|
|
||||||
verifyAnnotation(t, pod, "Initial Resources plugin set: cpu, memory request for init container i0; cpu, memory request for container c0; cpu request for container c1; memory request for container c2")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNamespaceAware(t *testing.T) {
|
|
||||||
f := func(_ api.ResourceName, _ int64, _, ns string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
if ns == "test-ns" {
|
|
||||||
return 200, 0, nil
|
|
||||||
}
|
|
||||||
return 200, 120, nil
|
|
||||||
}
|
|
||||||
ir := newInitialResources(&fakeSource{f: f}, 90, true)
|
|
||||||
|
|
||||||
pods := []*api.Pod{
|
|
||||||
createPod("p0", "image:v0", parseReq("", "")),
|
|
||||||
}
|
|
||||||
admit(t, ir, pods)
|
|
||||||
|
|
||||||
if pods[0].Spec.Containers[0].Resources.Requests != nil {
|
|
||||||
t.Errorf("Unexpected resource estimation")
|
|
||||||
}
|
|
||||||
|
|
||||||
expectNoAnnotation(t, pods[0])
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
influxdbHost = flag.String("ir-influxdb-host", "localhost:8080/api/v1/namespaces/kube-system/services/monitoring-influxdb:api/proxy", "Address of InfluxDB which contains metrics required by InitialResources")
|
|
||||||
user = flag.String("ir-user", "root", "User used for connecting to InfluxDB")
|
|
||||||
// TODO: figure out how to better pass password here
|
|
||||||
password = flag.String("ir-password", "root", "Password used for connecting to InfluxDB")
|
|
||||||
db = flag.String("ir-dbname", "k8s", "InfluxDB database name which contains metrics required by InitialResources")
|
|
||||||
hawkularConfig = flag.String("ir-hawkular", "", "Hawkular configuration URL")
|
|
||||||
)
|
|
||||||
|
|
||||||
// WARNING: If you are planning to add another implementation of dataSource interface please bear in mind,
|
|
||||||
// that dataSource will be moved to Heapster some time in the future and possibly rewritten.
|
|
||||||
type dataSource interface {
|
|
||||||
// Returns <perc>th of sample values which represent usage of <kind> for containers running <image>,
|
|
||||||
// within time range (start, end), number of samples considered and error if occurred.
|
|
||||||
// If <exactMatch> then take only samples that concern the same image (both name and take are the same),
|
|
||||||
// otherwise consider also samples with the same image a possibly different tag.
|
|
||||||
GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (usage int64, samples int64, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDataSource(kind string) (dataSource, error) {
|
|
||||||
if kind == "influxdb" {
|
|
||||||
return newInfluxdbSource(*influxdbHost, *user, *password, *db)
|
|
||||||
}
|
|
||||||
if kind == "gcm" {
|
|
||||||
return newGcmSource()
|
|
||||||
}
|
|
||||||
if kind == "hawkular" {
|
|
||||||
return newHawkularSource(*hawkularConfig)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("unknown data source %v", kind)
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 initialresources
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestInfluxDBDataSource(t *testing.T) {
|
|
||||||
ds, _ := newDataSource("influxdb")
|
|
||||||
if _, ok := ds.(*influxdbSource); !ok {
|
|
||||||
t.Errorf("newDataSource did not return valid InfluxDB type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGCMDataSource(t *testing.T) {
|
|
||||||
// No ProjectID set
|
|
||||||
newDataSource("gcm")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHawkularDataSource(t *testing.T) {
|
|
||||||
ds, _ := newDataSource("hawkular")
|
|
||||||
if _, ok := ds.(*hawkularSource); !ok {
|
|
||||||
t.Errorf("newDataSource did not return valid hawkularSource type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoDataSourceFound(t *testing.T) {
|
|
||||||
ds, err := newDataSource("")
|
|
||||||
if ds != nil || err == nil {
|
|
||||||
t.Errorf("newDataSource found for empty input")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
gce "cloud.google.com/go/compute/metadata"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/google"
|
|
||||||
gcm "google.golang.org/api/cloudmonitoring/v2beta2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
kubePrefix = "custom.cloudmonitoring.googleapis.com/kubernetes.io/"
|
|
||||||
cpuMetricName = kubePrefix + "cpu/usage_rate"
|
|
||||||
memMetricName = kubePrefix + "memory/usage"
|
|
||||||
labelImage = kubePrefix + "label/container_base_image"
|
|
||||||
labelNs = kubePrefix + "label/pod_namespace"
|
|
||||||
)
|
|
||||||
|
|
||||||
type gcmSource struct {
|
|
||||||
project string
|
|
||||||
gcmService *gcm.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGcmSource() (dataSource, error) {
|
|
||||||
// Detect project ID
|
|
||||||
projectId, err := gce.ProjectID()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Google Cloud Monitoring service.
|
|
||||||
client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
|
|
||||||
s, err := gcm.New(client)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &gcmSource{
|
|
||||||
project: projectId,
|
|
||||||
gcmService: s,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gcmSource) query(metric, oldest, youngest string, labels []string, pageToken string) (*gcm.ListTimeseriesResponse, error) {
|
|
||||||
req := s.gcmService.Timeseries.List(s.project, metric, youngest, nil).
|
|
||||||
Oldest(oldest).
|
|
||||||
Aggregator("mean").
|
|
||||||
Window("1m")
|
|
||||||
for _, l := range labels {
|
|
||||||
req = req.Labels(l)
|
|
||||||
}
|
|
||||||
if pageToken != "" {
|
|
||||||
req = req.PageToken(pageToken)
|
|
||||||
}
|
|
||||||
return req.Do()
|
|
||||||
}
|
|
||||||
|
|
||||||
func retrieveRawSamples(res *gcm.ListTimeseriesResponse, output *[]int) {
|
|
||||||
for _, ts := range res.Timeseries {
|
|
||||||
for _, p := range ts.Points {
|
|
||||||
*output = append(*output, int(*p.DoubleValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *gcmSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
var metric string
|
|
||||||
if kind == api.ResourceCPU {
|
|
||||||
metric = cpuMetricName
|
|
||||||
} else if kind == api.ResourceMemory {
|
|
||||||
metric = memMetricName
|
|
||||||
}
|
|
||||||
|
|
||||||
var labels []string
|
|
||||||
if exactMatch {
|
|
||||||
labels = append(labels, labelImage+"=="+image)
|
|
||||||
} else {
|
|
||||||
labels = append(labels, labelImage+"=~"+image+".*")
|
|
||||||
}
|
|
||||||
if namespace != "" {
|
|
||||||
labels = append(labels, labelNs+"=="+namespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
oldest := start.Format(time.RFC3339)
|
|
||||||
youngest := end.Format(time.RFC3339)
|
|
||||||
|
|
||||||
rawSamples := make([]int, 0)
|
|
||||||
pageToken := ""
|
|
||||||
for {
|
|
||||||
res, err := s.query(metric, oldest, youngest, labels, pageToken)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
retrieveRawSamples(res, &rawSamples)
|
|
||||||
|
|
||||||
pageToken = res.NextPageToken
|
|
||||||
if pageToken == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count := len(rawSamples)
|
|
||||||
if count == 0 {
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
||||||
sort.Ints(rawSamples)
|
|
||||||
usageIndex := int64(math.Ceil(float64(count)*9/10)) - 1
|
|
||||||
usage := rawSamples[usageIndex]
|
|
||||||
|
|
||||||
return int64(usage), int64(count), nil
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/google"
|
|
||||||
gcm "google.golang.org/api/cloudmonitoring/v2beta2"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGCMReturnsErrorIfClientCannotConnect(t *testing.T) {
|
|
||||||
client := oauth2.NewClient(oauth2.NoContext, google.ComputeTokenSource(""))
|
|
||||||
service, _ := gcm.New(client)
|
|
||||||
source := &gcmSource{
|
|
||||||
project: "",
|
|
||||||
gcmService: service,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err := source.GetUsagePercentile(api.ResourceCPU, 90, "", "", true, time.Now(), time.Now())
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error from GCM")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = source.GetUsagePercentile(api.ResourceMemory, 90, "", "foo", false, time.Now(), time.Now())
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error from GCM")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"github.com/hawkular/hawkular-client-go/metrics"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
)
|
|
||||||
|
|
||||||
type hawkularSource struct {
|
|
||||||
client *metrics.Client
|
|
||||||
uri *url.URL
|
|
||||||
useNamespace bool
|
|
||||||
modifiers []metrics.Modifier
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
containerImageTag string = "container_base_image"
|
|
||||||
descriptorTag string = "descriptor_name"
|
|
||||||
separator string = "/"
|
|
||||||
|
|
||||||
defaultServiceAccountFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
// heapsterName gets the equivalent MetricDescriptor.Name used in the Heapster
|
|
||||||
func heapsterName(kind api.ResourceName) string {
|
|
||||||
switch kind {
|
|
||||||
case api.ResourceCPU:
|
|
||||||
return "cpu/usage"
|
|
||||||
case api.ResourceMemory:
|
|
||||||
return "memory/usage"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tagQuery creates tagFilter query for Hawkular
|
|
||||||
func tagQuery(kind api.ResourceName, image string, exactMatch bool) map[string]string {
|
|
||||||
q := make(map[string]string)
|
|
||||||
|
|
||||||
// Add here the descriptor_tag..
|
|
||||||
q[descriptorTag] = heapsterName(kind)
|
|
||||||
|
|
||||||
if exactMatch {
|
|
||||||
q[containerImageTag] = image
|
|
||||||
} else {
|
|
||||||
split := strings.Index(image, "@")
|
|
||||||
if split < 0 {
|
|
||||||
split = strings.Index(image, ":")
|
|
||||||
}
|
|
||||||
q[containerImageTag] = fmt.Sprintf("%s:*", image[:split])
|
|
||||||
}
|
|
||||||
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
// dataSource API
|
|
||||||
|
|
||||||
func (hs *hawkularSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
q := tagQuery(kind, image, exactMatch)
|
|
||||||
|
|
||||||
m := make([]metrics.Modifier, len(hs.modifiers), 2+len(hs.modifiers))
|
|
||||||
copy(m, hs.modifiers)
|
|
||||||
|
|
||||||
if namespace != metav1.NamespaceAll {
|
|
||||||
m = append(m, metrics.Tenant(namespace))
|
|
||||||
}
|
|
||||||
|
|
||||||
p := float64(perc)
|
|
||||||
m = append(m, metrics.Filters(metrics.TagsFilter(q), metrics.BucketsFilter(1), metrics.StartTimeFilter(start), metrics.EndTimeFilter(end), metrics.PercentilesFilter([]float64{p})))
|
|
||||||
|
|
||||||
bp, err := hs.client.ReadBuckets(metrics.Counter, m...)
|
|
||||||
if err != nil {
|
|
||||||
return 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(bp) > 0 && len(bp[0].Percentiles) > 0 {
|
|
||||||
return int64(bp[0].Percentiles[0].Value), int64(bp[0].Samples), nil
|
|
||||||
}
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newHawkularSource creates a new Hawkular Source. The uri follows the scheme from Heapster
|
|
||||||
func newHawkularSource(uri string) (dataSource, error) {
|
|
||||||
u, err := url.Parse(uri)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d := &hawkularSource{
|
|
||||||
uri: u,
|
|
||||||
}
|
|
||||||
if err = d.init(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// init initializes the Hawkular dataSource. Almost equal to the Heapster initialization
|
|
||||||
func (hs *hawkularSource) init() error {
|
|
||||||
hs.modifiers = make([]metrics.Modifier, 0)
|
|
||||||
p := metrics.Parameters{
|
|
||||||
Tenant: "heapster", // This data is stored by the heapster - for no-namespace hits
|
|
||||||
Url: hs.uri.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := hs.uri.Query()
|
|
||||||
|
|
||||||
if v, found := opts["tenant"]; found {
|
|
||||||
p.Tenant = v[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, found := opts["useServiceAccount"]; found {
|
|
||||||
if b, _ := strconv.ParseBool(v[0]); b {
|
|
||||||
accountFile := defaultServiceAccountFile
|
|
||||||
if file, f := opts["serviceAccountFile"]; f {
|
|
||||||
accountFile = file[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a readable service account token exists, then use it
|
|
||||||
if contents, err := ioutil.ReadFile(accountFile); err == nil {
|
|
||||||
p.Token = string(contents)
|
|
||||||
} else {
|
|
||||||
glog.Errorf("Could not read contents of %s, no token authentication is used\n", defaultServiceAccountFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authentication / Authorization parameters
|
|
||||||
tC := &tls.Config{}
|
|
||||||
|
|
||||||
if v, found := opts["auth"]; found {
|
|
||||||
if _, f := opts["caCert"]; f {
|
|
||||||
return fmt.Errorf("both auth and caCert files provided, combination is not supported")
|
|
||||||
}
|
|
||||||
if len(v[0]) > 0 {
|
|
||||||
// Authfile
|
|
||||||
kubeConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(&clientcmd.ClientConfigLoadingRules{
|
|
||||||
ExplicitPath: v[0]},
|
|
||||||
&clientcmd.ConfigOverrides{}).ClientConfig()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tC, err = restclient.TLSConfigFor(kubeConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u, found := opts["user"]; found {
|
|
||||||
if _, wrong := opts["useServiceAccount"]; wrong {
|
|
||||||
return fmt.Errorf("if user and password are used, serviceAccount cannot be used")
|
|
||||||
}
|
|
||||||
if p, f := opts["pass"]; f {
|
|
||||||
hs.modifiers = append(hs.modifiers, func(req *http.Request) error {
|
|
||||||
req.SetBasicAuth(u[0], p[0])
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, found := opts["caCert"]; found {
|
|
||||||
caCert, err := ioutil.ReadFile(v[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
caCertPool := x509.NewCertPool()
|
|
||||||
caCertPool.AppendCertsFromPEM(caCert)
|
|
||||||
|
|
||||||
tC.RootCAs = caCertPool
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, found := opts["insecure"]; found {
|
|
||||||
insecure, err := strconv.ParseBool(v[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tC.InsecureSkipVerify = insecure
|
|
||||||
}
|
|
||||||
|
|
||||||
p.TLSConfig = tC
|
|
||||||
|
|
||||||
c, err := metrics.NewHawkularClient(p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hs.client = c
|
|
||||||
|
|
||||||
glog.Infof("Initialised Hawkular Source with parameters %v", p)
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
assert "github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
testImageName string = "hawkular/hawkular-metrics"
|
|
||||||
testImageVersion string = "latest"
|
|
||||||
testImageSHA string = "b727ece3780cdd30e9a86226e520f26bcc396071ed7a86b7ef6684bb93a9f717"
|
|
||||||
testPartialMatch string = "hawkular/hawkular-metrics:*"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testImageWithVersion() string {
|
|
||||||
return fmt.Sprintf("%s:%s", testImageName, testImageVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testImageWithReference() string {
|
|
||||||
return fmt.Sprintf("%s@sha256:%s", testImageName, testImageSHA)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTaqQuery(t *testing.T) {
|
|
||||||
kind := api.ResourceCPU
|
|
||||||
tQ := tagQuery(kind, testImageWithVersion(), false)
|
|
||||||
|
|
||||||
assert.Equal(t, 2, len(tQ))
|
|
||||||
assert.Equal(t, testPartialMatch, tQ[containerImageTag])
|
|
||||||
assert.Equal(t, "cpu/usage", tQ[descriptorTag])
|
|
||||||
|
|
||||||
tQe := tagQuery(kind, testImageWithVersion(), true)
|
|
||||||
assert.Equal(t, 2, len(tQe))
|
|
||||||
assert.Equal(t, testImageWithVersion(), tQe[containerImageTag])
|
|
||||||
assert.Equal(t, "cpu/usage", tQe[descriptorTag])
|
|
||||||
|
|
||||||
tQr := tagQuery(kind, testImageWithReference(), false)
|
|
||||||
assert.Equal(t, 2, len(tQe))
|
|
||||||
assert.Equal(t, testPartialMatch, tQr[containerImageTag])
|
|
||||||
assert.Equal(t, "cpu/usage", tQr[descriptorTag])
|
|
||||||
|
|
||||||
tQre := tagQuery(kind, testImageWithReference(), true)
|
|
||||||
assert.Equal(t, 2, len(tQe))
|
|
||||||
assert.Equal(t, testImageWithReference(), tQre[containerImageTag])
|
|
||||||
assert.Equal(t, "cpu/usage", tQre[descriptorTag])
|
|
||||||
|
|
||||||
kind = api.ResourceMemory
|
|
||||||
tQ = tagQuery(kind, testImageWithReference(), true)
|
|
||||||
assert.Equal(t, "memory/usage", tQ[descriptorTag])
|
|
||||||
|
|
||||||
kind = api.ResourceStorage
|
|
||||||
tQ = tagQuery(kind, testImageWithReference(), true)
|
|
||||||
assert.Equal(t, "", tQ[descriptorTag])
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSource(t *testing.T) (map[string]string, dataSource) {
|
|
||||||
tenant := "16a8884e4c155457ee38a8901df6b536"
|
|
||||||
reqs := make(map[string]string)
|
|
||||||
|
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
assert.Equal(t, tenant, r.Header.Get("Hawkular-Tenant"))
|
|
||||||
assert.Equal(t, "Basic", r.Header.Get("Authorization")[:5])
|
|
||||||
|
|
||||||
if strings.Contains(r.RequestURI, "counters/data") {
|
|
||||||
assert.True(t, strings.Contains(r.RequestURI, url.QueryEscape(testImageWithVersion())))
|
|
||||||
assert.True(t, strings.Contains(r.RequestURI, "cpu%2Fusage"))
|
|
||||||
assert.True(t, strings.Contains(r.RequestURI, "percentiles=90"))
|
|
||||||
|
|
||||||
reqs["counters/data"] = r.RequestURI
|
|
||||||
fmt.Fprintf(w, ` [{"start":1444620095882,"end":1444648895882,"min":1.45,"avg":1.45,"median":1.45,"max":1.45,"percentile95th":1.45,"samples":123456,"percentiles":[{"value":7896.54,"quantile":0.9},{"value":1.45,"quantile":0.99}],"empty":false}]`)
|
|
||||||
} else {
|
|
||||||
reqs["unknown"] = r.RequestURI
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
|
|
||||||
paramUri := fmt.Sprintf("%s?user=test&pass=yep&tenant=foo&insecure=true", s.URL)
|
|
||||||
|
|
||||||
hSource, err := newHawkularSource(paramUri)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
return reqs, hSource
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsecureMustBeBool(t *testing.T) {
|
|
||||||
paramUri := fmt.Sprintf("localhost?user=test&pass=yep&insecure=foo")
|
|
||||||
_, err := newHawkularSource(paramUri)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error from newHawkularSource")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCAFileMustExist(t *testing.T) {
|
|
||||||
paramUri := fmt.Sprintf("localhost?user=test&pass=yep&caCert=foo")
|
|
||||||
_, err := newHawkularSource(paramUri)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error from newHawkularSource")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceAccountIsMutuallyExclusiveWithAuth(t *testing.T) {
|
|
||||||
paramUri := fmt.Sprintf("localhost?user=test&pass=yep&useServiceAccount=true")
|
|
||||||
_, err := newHawkularSource(paramUri)
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error from newHawkularSource")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetUsagePercentile(t *testing.T) {
|
|
||||||
reqs, hSource := newSource(t)
|
|
||||||
|
|
||||||
usage, samples, err := hSource.GetUsagePercentile(api.ResourceCPU, 90, testImageWithVersion(), "16a8884e4c155457ee38a8901df6b536", true, time.Now(), time.Now())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, 1, len(reqs))
|
|
||||||
assert.Equal(t, "", reqs["unknown"])
|
|
||||||
|
|
||||||
assert.Equal(t, int64(123456), int64(samples))
|
|
||||||
assert.Equal(t, int64(7896), usage) // float64 -> int64
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2015 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
influxdb "github.com/influxdata/influxdb/client"
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cpuSeriesName = "autoscaling.cpu.usage.2m"
|
|
||||||
memSeriesName = "autoscaling.memory.usage.2m"
|
|
||||||
cpuContinuousQuery = "select derivative(value) as value from \"cpu/usage_ns_cumulative\" where pod_id <> '' group by pod_id, pod_namespace, container_name, container_base_image, time(2m) into " + cpuSeriesName
|
|
||||||
memContinuousQuery = "select mean(value) as value from \"memory/usage_bytes_gauge\" where pod_id <> '' group by pod_id, pod_namespace, container_name, container_base_image, time(2m) into " + memSeriesName
|
|
||||||
timeFormat = "2006-01-02 15:04:05"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO(piosz): rewrite this once we will migrate into InfluxDB v0.9.
|
|
||||||
type influxdbSource struct{}
|
|
||||||
|
|
||||||
func newInfluxdbSource(host, user, password, db string) (dataSource, error) {
|
|
||||||
return &influxdbSource{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *influxdbSource) query(query string) ([]*influxdb.Response, error) {
|
|
||||||
// TODO(piosz): add support again
|
|
||||||
return nil, fmt.Errorf("temporary not supported; see #18826 for more details")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *influxdbSource) GetUsagePercentile(kind api.ResourceName, perc int64, image, namespace string, exactMatch bool, start, end time.Time) (int64, int64, error) {
|
|
||||||
var series string
|
|
||||||
if kind == api.ResourceCPU {
|
|
||||||
series = cpuSeriesName
|
|
||||||
} else if kind == api.ResourceMemory {
|
|
||||||
series = memSeriesName
|
|
||||||
}
|
|
||||||
|
|
||||||
var imgPattern string
|
|
||||||
if exactMatch {
|
|
||||||
imgPattern = "='" + image + "'"
|
|
||||||
} else {
|
|
||||||
// Escape character "/" in image pattern.
|
|
||||||
imgPattern = "=~/^" + strings.Replace(image, "/", "\\/", -1) + "/"
|
|
||||||
}
|
|
||||||
var namespaceCond string
|
|
||||||
if namespace != "" {
|
|
||||||
namespaceCond = " and pod_namespace='" + namespace + "'"
|
|
||||||
}
|
|
||||||
|
|
||||||
query := fmt.Sprintf("select percentile(value, %v), count(pod_id) from %v where container_base_image%v%v and time > '%v' and time < '%v'", perc, series, imgPattern, namespaceCond, start.UTC().Format(timeFormat), end.UTC().Format(timeFormat))
|
|
||||||
if _, err := s.query(query); err != nil {
|
|
||||||
return 0, 0, fmt.Errorf("error while trying to query InfluxDB: %v", err)
|
|
||||||
}
|
|
||||||
return 0, 0, nil
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 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 initialresources
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInfluxDBGetUsagePercentileCPU(t *testing.T) {
|
|
||||||
source, _ := newInfluxdbSource("", "", "", "")
|
|
||||||
_, _, err := source.GetUsagePercentile(api.ResourceCPU, 90, "", "", true, time.Now(), time.Now())
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error because InfluxDB is temporarily disabled")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInfluxDBGetUsagePercentileMemory(t *testing.T) {
|
|
||||||
source, _ := newInfluxdbSource("", "", "", "")
|
|
||||||
_, _, err := source.GetUsagePercentile(api.ResourceMemory, 90, "", "foo", false, time.Now(), time.Now())
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("Expected error because InfluxDB is temporarily disabled")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -847,7 +847,6 @@ k8s.io/kubernetes/plugin/pkg/admission/deny,eparis,1,
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/exec,deads2k,1,
|
k8s.io/kubernetes/plugin/pkg/admission/exec,deads2k,1,
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/gc,kevin-wangzefeng,1,
|
k8s.io/kubernetes/plugin/pkg/admission/gc,kevin-wangzefeng,1,
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/imagepolicy,apelisse,1,
|
k8s.io/kubernetes/plugin/pkg/admission/imagepolicy,apelisse,1,
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/initialresources,piosz,0,
|
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/limitranger,ncdc,1,
|
k8s.io/kubernetes/plugin/pkg/admission/limitranger,ncdc,1,
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision,derekwaynecarr,0,
|
k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision,derekwaynecarr,0,
|
||||||
k8s.io/kubernetes/plugin/pkg/admission/namespace/exists,derekwaynecarr,0,
|
k8s.io/kubernetes/plugin/pkg/admission/namespace/exists,derekwaynecarr,0,
|
||||||
|
|
|
Loading…
Reference in New Issue