mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
vendor files
This commit is contained in:
46
vendor/k8s.io/kubernetes/plugin/BUILD
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/plugin/BUILD
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//plugin/cmd/kube-scheduler:all-srcs",
|
||||
"//plugin/pkg/admission/admit:all-srcs",
|
||||
"//plugin/pkg/admission/alwayspullimages:all-srcs",
|
||||
"//plugin/pkg/admission/antiaffinity:all-srcs",
|
||||
"//plugin/pkg/admission/defaulttolerationseconds:all-srcs",
|
||||
"//plugin/pkg/admission/deny:all-srcs",
|
||||
"//plugin/pkg/admission/eventratelimit:all-srcs",
|
||||
"//plugin/pkg/admission/exec:all-srcs",
|
||||
"//plugin/pkg/admission/extendedresourcetoleration:all-srcs",
|
||||
"//plugin/pkg/admission/gc:all-srcs",
|
||||
"//plugin/pkg/admission/imagepolicy:all-srcs",
|
||||
"//plugin/pkg/admission/initialresources:all-srcs",
|
||||
"//plugin/pkg/admission/limitranger:all-srcs",
|
||||
"//plugin/pkg/admission/namespace/autoprovision:all-srcs",
|
||||
"//plugin/pkg/admission/namespace/exists:all-srcs",
|
||||
"//plugin/pkg/admission/noderestriction:all-srcs",
|
||||
"//plugin/pkg/admission/persistentvolume/label:all-srcs",
|
||||
"//plugin/pkg/admission/persistentvolume/resize:all-srcs",
|
||||
"//plugin/pkg/admission/persistentvolumeclaim/pvcprotection:all-srcs",
|
||||
"//plugin/pkg/admission/podnodeselector:all-srcs",
|
||||
"//plugin/pkg/admission/podpreset:all-srcs",
|
||||
"//plugin/pkg/admission/podtolerationrestriction:all-srcs",
|
||||
"//plugin/pkg/admission/priority:all-srcs",
|
||||
"//plugin/pkg/admission/resourcequota:all-srcs",
|
||||
"//plugin/pkg/admission/security:all-srcs",
|
||||
"//plugin/pkg/admission/securitycontext/scdeny:all-srcs",
|
||||
"//plugin/pkg/admission/serviceaccount:all-srcs",
|
||||
"//plugin/pkg/admission/storageclass/setdefault:all-srcs",
|
||||
"//plugin/pkg/auth:all-srcs",
|
||||
"//plugin/pkg/scheduler:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
12
vendor/k8s.io/kubernetes/plugin/OWNERS
generated
vendored
Normal file
12
vendor/k8s.io/kubernetes/plugin/OWNERS
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
reviewers:
|
||||
- brendandburns
|
||||
- davidopp
|
||||
- dchen1107
|
||||
- lavalamp
|
||||
- thockin
|
||||
approvers:
|
||||
- brendandburns
|
||||
- davidopp
|
||||
- dchen1107
|
||||
- lavalamp
|
||||
- thockin
|
51
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/BUILD
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/BUILD
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
)
|
||||
load("//pkg/version:def.bzl", "version_x_defs")
|
||||
|
||||
go_binary(
|
||||
name = "kube-scheduler",
|
||||
gc_linkopts = [
|
||||
"-linkmode",
|
||||
"external",
|
||||
"-extldflags",
|
||||
"-static",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler",
|
||||
library = ":go_default_library",
|
||||
x_defs = version_x_defs(),
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["scheduler.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler",
|
||||
deps = [
|
||||
"//pkg/client/metrics/prometheus:go_default_library",
|
||||
"//pkg/version/prometheus:go_default_library",
|
||||
"//plugin/cmd/kube-scheduler/app:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/flag:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/logs:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//plugin/cmd/kube-scheduler/app:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
4
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/OWNERS
generated
vendored
Normal file
4
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/OWNERS
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
approvers:
|
||||
- sig-scheduling-maintainers
|
||||
reviewers:
|
||||
- sig-scheduling
|
66
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/BUILD
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/BUILD
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["server.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/cmd/kube-scheduler/app",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/componentconfig:go_default_library",
|
||||
"//pkg/apis/componentconfig/v1alpha1:go_default_library",
|
||||
"//pkg/client/leaderelectionconfig:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/master/ports:go_default_library",
|
||||
"//pkg/util/configz:go_default_library",
|
||||
"//pkg/version:go_default_library",
|
||||
"//pkg/version/verflag:go_default_library",
|
||||
"//plugin/pkg/scheduler:go_default_library",
|
||||
"//plugin/pkg/scheduler/algorithmprovider:go_default_library",
|
||||
"//plugin/pkg/scheduler/api:go_default_library",
|
||||
"//plugin/pkg/scheduler/api/latest:go_default_library",
|
||||
"//plugin/pkg/scheduler/factory:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/prometheus/client_golang/prometheus:go_default_library",
|
||||
"//vendor/github.com/spf13/cobra:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/healthz:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/informers/storage/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/leaderelection:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/leaderelection/resourcelock:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
708
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/server.go
generated
vendored
Normal file
708
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/app/server.go
generated
vendored
Normal file
@ -0,0 +1,708 @@
|
||||
/*
|
||||
Copyright 2014 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 app implements a Server object for running the scheduler.
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"reflect"
|
||||
goruntime "runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/server/healthz"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
storageinformers "k8s.io/client-go/informers/storage/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/client-go/tools/leaderelection"
|
||||
"k8s.io/client-go/tools/leaderelection/resourcelock"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/componentconfig"
|
||||
componentconfigv1alpha1 "k8s.io/kubernetes/pkg/apis/componentconfig/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/client/leaderelectionconfig"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/master/ports"
|
||||
"k8s.io/kubernetes/pkg/util/configz"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
"k8s.io/kubernetes/pkg/version/verflag"
|
||||
"k8s.io/kubernetes/plugin/pkg/scheduler"
|
||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider"
|
||||
schedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api"
|
||||
latestschedulerapi "k8s.io/kubernetes/plugin/pkg/scheduler/api/latest"
|
||||
|
||||
"k8s.io/kubernetes/plugin/pkg/scheduler/factory"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
// SchedulerServer has all the context and params needed to run a Scheduler
|
||||
type Options struct {
|
||||
// ConfigFile is the location of the scheduler server's configuration file.
|
||||
ConfigFile string
|
||||
|
||||
// config is the scheduler server's configuration object.
|
||||
config *componentconfig.KubeSchedulerConfiguration
|
||||
|
||||
scheme *runtime.Scheme
|
||||
codecs serializer.CodecFactory
|
||||
|
||||
// The fields below here are placeholders for flags that can't be directly
|
||||
// mapped into componentconfig.KubeSchedulerConfiguration.
|
||||
//
|
||||
// TODO remove these fields once the deprecated flags are removed.
|
||||
|
||||
// master is the address of the Kubernetes API server (overrides any
|
||||
// value in kubeconfig).
|
||||
master string
|
||||
healthzAddress string
|
||||
healthzPort int32
|
||||
policyConfigFile string
|
||||
policyConfigMapName string
|
||||
policyConfigMapNamespace string
|
||||
useLegacyPolicyConfig bool
|
||||
algorithmProvider string
|
||||
}
|
||||
|
||||
// AddFlags adds flags for a specific SchedulerServer to the specified FlagSet
|
||||
func AddFlags(options *Options, fs *pflag.FlagSet) {
|
||||
fs.StringVar(&options.ConfigFile, "config", options.ConfigFile, "The path to the configuration file.")
|
||||
|
||||
// All flags below here are deprecated and will eventually be removed.
|
||||
|
||||
fs.Int32Var(&options.healthzPort, "port", ports.SchedulerPort, "The port that the scheduler's http service runs on")
|
||||
fs.StringVar(&options.healthzAddress, "address", options.healthzAddress, "The IP address to serve on (set to 0.0.0.0 for all interfaces)")
|
||||
fs.StringVar(&options.algorithmProvider, "algorithm-provider", options.algorithmProvider, "The scheduling algorithm provider to use, one of: "+factory.ListAlgorithmProviders())
|
||||
fs.StringVar(&options.policyConfigFile, "policy-config-file", options.policyConfigFile, "File with scheduler policy configuration. This file is used if policy ConfigMap is not provided or --use-legacy-policy-config==true")
|
||||
usage := fmt.Sprintf("Name of the ConfigMap object that contains scheduler's policy configuration. It must exist in the system namespace before scheduler initialization if --use-legacy-policy-config==false. The config must be provided as the value of an element in 'Data' map with the key='%v'", componentconfig.SchedulerPolicyConfigMapKey)
|
||||
fs.StringVar(&options.policyConfigMapName, "policy-configmap", options.policyConfigMapName, usage)
|
||||
fs.StringVar(&options.policyConfigMapNamespace, "policy-configmap-namespace", options.policyConfigMapNamespace, "The namespace where policy ConfigMap is located. The system namespace will be used if this is not provided or is empty.")
|
||||
fs.BoolVar(&options.useLegacyPolicyConfig, "use-legacy-policy-config", false, "When set to true, scheduler will ignore policy ConfigMap and uses policy config file")
|
||||
fs.BoolVar(&options.config.EnableProfiling, "profiling", options.config.EnableProfiling, "Enable profiling via web interface host:port/debug/pprof/")
|
||||
fs.BoolVar(&options.config.EnableContentionProfiling, "contention-profiling", options.config.EnableContentionProfiling, "Enable lock contention profiling, if profiling is enabled")
|
||||
fs.StringVar(&options.master, "master", options.master, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
|
||||
fs.StringVar(&options.config.ClientConnection.KubeConfigFile, "kubeconfig", options.config.ClientConnection.KubeConfigFile, "Path to kubeconfig file with authorization and master location information.")
|
||||
fs.StringVar(&options.config.ClientConnection.ContentType, "kube-api-content-type", options.config.ClientConnection.ContentType, "Content type of requests sent to apiserver.")
|
||||
fs.Float32Var(&options.config.ClientConnection.QPS, "kube-api-qps", options.config.ClientConnection.QPS, "QPS to use while talking with kubernetes apiserver")
|
||||
fs.Int32Var(&options.config.ClientConnection.Burst, "kube-api-burst", options.config.ClientConnection.Burst, "Burst to use while talking with kubernetes apiserver")
|
||||
fs.StringVar(&options.config.SchedulerName, "scheduler-name", options.config.SchedulerName, "Name of the scheduler, used to select which pods will be processed by this scheduler, based on pod's \"spec.SchedulerName\".")
|
||||
fs.StringVar(&options.config.LeaderElection.LockObjectNamespace, "lock-object-namespace", options.config.LeaderElection.LockObjectNamespace, "Define the namespace of the lock object.")
|
||||
fs.StringVar(&options.config.LeaderElection.LockObjectName, "lock-object-name", options.config.LeaderElection.LockObjectName, "Define the name of the lock object.")
|
||||
fs.Int32Var(&options.config.HardPodAffinitySymmetricWeight, "hard-pod-affinity-symmetric-weight", options.config.HardPodAffinitySymmetricWeight,
|
||||
"RequiredDuringScheduling affinity is not symmetric, but there is an implicit PreferredDuringScheduling affinity rule corresponding "+
|
||||
"to every RequiredDuringScheduling affinity rule. --hard-pod-affinity-symmetric-weight represents the weight of implicit PreferredDuringScheduling affinity rule.")
|
||||
fs.MarkDeprecated("hard-pod-affinity-symmetric-weight", "This option was moved to the policy configuration file")
|
||||
fs.StringVar(&options.config.FailureDomains, "failure-domains", options.config.FailureDomains, "Indicate the \"all topologies\" set for an empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity.")
|
||||
fs.MarkDeprecated("failure-domains", "Doesn't have any effect. Will be removed in future version.")
|
||||
leaderelectionconfig.BindFlags(&options.config.LeaderElection.LeaderElectionConfiguration, fs)
|
||||
utilfeature.DefaultFeatureGate.AddFlag(fs)
|
||||
}
|
||||
|
||||
func NewOptions() (*Options, error) {
|
||||
o := &Options{
|
||||
config: new(componentconfig.KubeSchedulerConfiguration),
|
||||
}
|
||||
|
||||
o.scheme = runtime.NewScheme()
|
||||
o.codecs = serializer.NewCodecFactory(o.scheme)
|
||||
|
||||
if err := componentconfig.AddToScheme(o.scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := componentconfigv1alpha1.AddToScheme(o.scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return o, nil
|
||||
}
|
||||
|
||||
func (o *Options) Complete() error {
|
||||
if len(o.ConfigFile) == 0 {
|
||||
glog.Warning("WARNING: all flags than --config are deprecated. Please begin using a config file ASAP.")
|
||||
o.applyDeprecatedHealthzAddressToConfig()
|
||||
o.applyDeprecatedHealthzPortToConfig()
|
||||
o.applyDeprecatedAlgorithmSourceOptionsToConfig()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyDeprecatedHealthzAddressToConfig sets o.config.HealthzBindAddress and
|
||||
// o.config.MetricsBindAddress from flags passed on the command line based on
|
||||
// the following rules:
|
||||
//
|
||||
// 1. If --address is empty, leave the config as-is.
|
||||
// 2. Otherwise, use the value of --address for the address portion of
|
||||
// o.config.HealthzBindAddress
|
||||
func (o *Options) applyDeprecatedHealthzAddressToConfig() {
|
||||
if len(o.healthzAddress) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
_, port, err := net.SplitHostPort(o.config.HealthzBindAddress)
|
||||
if err != nil {
|
||||
glog.Fatalf("invalid healthz bind address %q: %v", o.config.HealthzBindAddress, err)
|
||||
}
|
||||
o.config.HealthzBindAddress = net.JoinHostPort(o.healthzAddress, port)
|
||||
o.config.MetricsBindAddress = net.JoinHostPort(o.healthzAddress, port)
|
||||
}
|
||||
|
||||
// applyDeprecatedHealthzPortToConfig sets o.config.HealthzBindAddress and
|
||||
// o.config.MetricsBindAddress from flags passed on the command line based on
|
||||
// the following rules:
|
||||
//
|
||||
// 1. If --port is -1, disable the healthz server.
|
||||
// 2. Otherwise, use the value of --port for the port portion of
|
||||
// o.config.HealthzBindAddress
|
||||
func (o *Options) applyDeprecatedHealthzPortToConfig() {
|
||||
if o.healthzPort == -1 {
|
||||
o.config.HealthzBindAddress = ""
|
||||
return
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(o.config.HealthzBindAddress)
|
||||
if err != nil {
|
||||
glog.Fatalf("invalid healthz bind address %q: %v", o.config.HealthzBindAddress, err)
|
||||
}
|
||||
o.config.HealthzBindAddress = net.JoinHostPort(host, strconv.Itoa(int(o.healthzPort)))
|
||||
o.config.MetricsBindAddress = net.JoinHostPort(host, strconv.Itoa(int(o.healthzPort)))
|
||||
}
|
||||
|
||||
// applyDeprecatedAlgorithmSourceOptionsToConfig sets o.config.AlgorithmSource from
|
||||
// flags passed on the command line in the following precedence order:
|
||||
//
|
||||
// 1. --use-legacy-policy-config to use a policy file.
|
||||
// 2. --policy-configmap to use a policy config map value.
|
||||
// 3. --algorithm-provider to use a named algorithm provider.
|
||||
func (o *Options) applyDeprecatedAlgorithmSourceOptionsToConfig() {
|
||||
switch {
|
||||
case o.useLegacyPolicyConfig:
|
||||
o.config.AlgorithmSource = componentconfig.SchedulerAlgorithmSource{
|
||||
Policy: &componentconfig.SchedulerPolicySource{
|
||||
File: &componentconfig.SchedulerPolicyFileSource{
|
||||
Path: o.policyConfigFile,
|
||||
},
|
||||
},
|
||||
}
|
||||
case len(o.policyConfigMapName) > 0:
|
||||
o.config.AlgorithmSource = componentconfig.SchedulerAlgorithmSource{
|
||||
Policy: &componentconfig.SchedulerPolicySource{
|
||||
ConfigMap: &componentconfig.SchedulerPolicyConfigMapSource{
|
||||
Name: o.policyConfigMapName,
|
||||
Namespace: o.policyConfigMapNamespace,
|
||||
},
|
||||
},
|
||||
}
|
||||
case len(o.algorithmProvider) > 0:
|
||||
o.config.AlgorithmSource = componentconfig.SchedulerAlgorithmSource{
|
||||
Provider: &o.algorithmProvider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates all the required options.
|
||||
func (o *Options) Validate(args []string) error {
|
||||
if len(args) != 0 {
|
||||
return errors.New("no arguments are supported")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadConfigFromFile loads the contents of file and decodes it as a
|
||||
// KubeSchedulerConfiguration object.
|
||||
func (o *Options) loadConfigFromFile(file string) (*componentconfig.KubeSchedulerConfiguration, error) {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return o.loadConfig(data)
|
||||
}
|
||||
|
||||
// loadConfig decodes data as a KubeSchedulerConfiguration object.
|
||||
func (o *Options) loadConfig(data []byte) (*componentconfig.KubeSchedulerConfiguration, error) {
|
||||
configObj, gvk, err := o.codecs.UniversalDecoder().Decode(data, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, ok := configObj.(*componentconfig.KubeSchedulerConfiguration)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("got unexpected config type: %v", gvk)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (o *Options) ApplyDefaults(in *componentconfig.KubeSchedulerConfiguration) (*componentconfig.KubeSchedulerConfiguration, error) {
|
||||
external, err := o.scheme.ConvertToVersion(in, componentconfigv1alpha1.SchemeGroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o.scheme.Default(external)
|
||||
|
||||
internal, err := o.scheme.ConvertToVersion(external, componentconfig.SchemeGroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := internal.(*componentconfig.KubeSchedulerConfiguration)
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (o *Options) Run() error {
|
||||
config := o.config
|
||||
|
||||
if len(o.ConfigFile) > 0 {
|
||||
if c, err := o.loadConfigFromFile(o.ConfigFile); err != nil {
|
||||
return err
|
||||
} else {
|
||||
config = c
|
||||
}
|
||||
}
|
||||
|
||||
// Apply algorithms based on feature gates.
|
||||
// TODO: make configurable?
|
||||
algorithmprovider.ApplyFeatureGates()
|
||||
|
||||
server, err := NewSchedulerServer(config, o.master)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stop := make(chan struct{})
|
||||
return server.Run(stop)
|
||||
}
|
||||
|
||||
// NewSchedulerCommand creates a *cobra.Command object with default parameters
|
||||
func NewSchedulerCommand() *cobra.Command {
|
||||
opts, err := NewOptions()
|
||||
if err != nil {
|
||||
glog.Fatalf("unable to initialize command options: %v", err)
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "kube-scheduler",
|
||||
Long: `The Kubernetes scheduler is a policy-rich, topology-aware,
|
||||
workload-specific function that significantly impacts availability, performance,
|
||||
and capacity. The scheduler needs to take into account individual and collective
|
||||
resource requirements, quality of service requirements, hardware/software/policy
|
||||
constraints, affinity and anti-affinity specifications, data locality, inter-workload
|
||||
interference, deadlines, and so on. Workload-specific requirements will be exposed
|
||||
through the API as necessary.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
verflag.PrintAndExitIfRequested()
|
||||
cmdutil.CheckErr(opts.Complete())
|
||||
cmdutil.CheckErr(opts.Validate(args))
|
||||
cmdutil.CheckErr(opts.Run())
|
||||
},
|
||||
}
|
||||
|
||||
opts.config, err = opts.ApplyDefaults(opts.config)
|
||||
if err != nil {
|
||||
glog.Fatalf("unable to apply config defaults: %v", err)
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
AddFlags(opts, flags)
|
||||
|
||||
cmd.MarkFlagFilename("config", "yaml", "yml", "json")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// SchedulerServer represents all the parameters required to start the
|
||||
// Kubernetes scheduler server.
|
||||
type SchedulerServer struct {
|
||||
SchedulerName string
|
||||
Client clientset.Interface
|
||||
InformerFactory informers.SharedInformerFactory
|
||||
PodInformer coreinformers.PodInformer
|
||||
AlgorithmSource componentconfig.SchedulerAlgorithmSource
|
||||
HardPodAffinitySymmetricWeight int32
|
||||
EventClient v1core.EventsGetter
|
||||
Recorder record.EventRecorder
|
||||
Broadcaster record.EventBroadcaster
|
||||
// LeaderElection is optional.
|
||||
LeaderElection *leaderelection.LeaderElectionConfig
|
||||
// HealthzServer is optional.
|
||||
HealthzServer *http.Server
|
||||
// MetricsServer is optional.
|
||||
MetricsServer *http.Server
|
||||
}
|
||||
|
||||
// NewSchedulerServer creates a runnable SchedulerServer from configuration.
|
||||
func NewSchedulerServer(config *componentconfig.KubeSchedulerConfiguration, master string) (*SchedulerServer, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("config is required")
|
||||
}
|
||||
|
||||
// Configz registration.
|
||||
if c, err := configz.New("componentconfig"); err == nil {
|
||||
c.Set(config)
|
||||
} else {
|
||||
return nil, fmt.Errorf("unable to register configz: %s", err)
|
||||
}
|
||||
|
||||
// Prepare some Kube clients.
|
||||
client, leaderElectionClient, eventClient, err := createClients(config.ClientConnection, master)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prepare event clients.
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
recorder := eventBroadcaster.NewRecorder(legacyscheme.Scheme, v1.EventSource{Component: config.SchedulerName})
|
||||
|
||||
// Set up leader election if enabled.
|
||||
var leaderElectionConfig *leaderelection.LeaderElectionConfig
|
||||
if config.LeaderElection.LeaderElect {
|
||||
leaderElectionConfig, err = makeLeaderElectionConfig(config.LeaderElection, leaderElectionClient, recorder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare a healthz server. If the metrics bind address is the same as the
|
||||
// healthz bind address, consolidate the servers into one.
|
||||
var healthzServer *http.Server
|
||||
if len(config.HealthzBindAddress) != 0 {
|
||||
healthzServer = makeHealthzServer(config)
|
||||
}
|
||||
|
||||
// Prepare a separate metrics server only if the bind address differs from the
|
||||
// healthz bind address.
|
||||
var metricsServer *http.Server
|
||||
if len(config.MetricsBindAddress) > 0 && config.HealthzBindAddress != config.MetricsBindAddress {
|
||||
metricsServer = makeMetricsServer(config)
|
||||
}
|
||||
|
||||
return &SchedulerServer{
|
||||
SchedulerName: config.SchedulerName,
|
||||
Client: client,
|
||||
InformerFactory: informers.NewSharedInformerFactory(client, 0),
|
||||
PodInformer: factory.NewPodInformer(client, 0, config.SchedulerName),
|
||||
AlgorithmSource: config.AlgorithmSource,
|
||||
HardPodAffinitySymmetricWeight: config.HardPodAffinitySymmetricWeight,
|
||||
EventClient: eventClient,
|
||||
Recorder: recorder,
|
||||
Broadcaster: eventBroadcaster,
|
||||
LeaderElection: leaderElectionConfig,
|
||||
HealthzServer: healthzServer,
|
||||
MetricsServer: metricsServer,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// makeLeaderElectionConfig builds a leader election configuration. It will
|
||||
// create a new resource lock associated with the configuration.
|
||||
func makeLeaderElectionConfig(config componentconfig.KubeSchedulerLeaderElectionConfiguration, client clientset.Interface, recorder record.EventRecorder) (*leaderelection.LeaderElectionConfig, error) {
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get hostname: %v", err)
|
||||
}
|
||||
|
||||
rl, err := resourcelock.New(config.ResourceLock,
|
||||
config.LockObjectNamespace,
|
||||
config.LockObjectName,
|
||||
client.CoreV1(),
|
||||
resourcelock.ResourceLockConfig{
|
||||
Identity: hostname,
|
||||
EventRecorder: recorder,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create resource lock: %v", err)
|
||||
}
|
||||
|
||||
return &leaderelection.LeaderElectionConfig{
|
||||
Lock: rl,
|
||||
LeaseDuration: config.LeaseDuration.Duration,
|
||||
RenewDeadline: config.RenewDeadline.Duration,
|
||||
RetryPeriod: config.RetryPeriod.Duration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// makeHealthzServer creates a healthz server from the config, and will also
|
||||
// embed the metrics handler if the healthz and metrics address configurations
|
||||
// are the same.
|
||||
func makeHealthzServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server {
|
||||
mux := http.NewServeMux()
|
||||
healthz.InstallHandler(mux)
|
||||
if config.HealthzBindAddress == config.MetricsBindAddress {
|
||||
configz.InstallHandler(mux)
|
||||
mux.Handle("/metrics", prometheus.Handler())
|
||||
}
|
||||
if config.EnableProfiling {
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
if config.EnableContentionProfiling {
|
||||
goruntime.SetBlockProfileRate(1)
|
||||
}
|
||||
}
|
||||
return &http.Server{
|
||||
Addr: config.HealthzBindAddress,
|
||||
Handler: mux,
|
||||
}
|
||||
}
|
||||
|
||||
// makeMetricsServer builds a metrics server from the config.
|
||||
func makeMetricsServer(config *componentconfig.KubeSchedulerConfiguration) *http.Server {
|
||||
mux := http.NewServeMux()
|
||||
configz.InstallHandler(mux)
|
||||
mux.Handle("/metrics", prometheus.Handler())
|
||||
if config.EnableProfiling {
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||
if config.EnableContentionProfiling {
|
||||
goruntime.SetBlockProfileRate(1)
|
||||
}
|
||||
}
|
||||
return &http.Server{
|
||||
Addr: config.MetricsBindAddress,
|
||||
Handler: mux,
|
||||
}
|
||||
}
|
||||
|
||||
// createClients creates a kube client and an event client from the given config and masterOverride.
|
||||
// TODO remove masterOverride when CLI flags are removed.
|
||||
func createClients(config componentconfig.ClientConnectionConfiguration, masterOverride string) (clientset.Interface, clientset.Interface, v1core.EventsGetter, error) {
|
||||
if len(config.KubeConfigFile) == 0 && len(masterOverride) == 0 {
|
||||
glog.Warningf("Neither --kubeconfig nor --master was specified. Using default API client. This might not work.")
|
||||
}
|
||||
|
||||
// This creates a client, first loading any specified kubeconfig
|
||||
// file, and then overriding the Master flag, if non-empty.
|
||||
kubeConfig, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{ExplicitPath: config.KubeConfigFile},
|
||||
&clientcmd.ConfigOverrides{ClusterInfo: clientcmdapi.Cluster{Server: masterOverride}}).ClientConfig()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
kubeConfig.AcceptContentTypes = config.AcceptContentTypes
|
||||
kubeConfig.ContentType = config.ContentType
|
||||
kubeConfig.QPS = config.QPS
|
||||
//TODO make config struct use int instead of int32?
|
||||
kubeConfig.Burst = int(config.Burst)
|
||||
|
||||
client, err := clientset.NewForConfig(restclient.AddUserAgent(kubeConfig, "scheduler"))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
leaderElectionClient, err := clientset.NewForConfig(restclient.AddUserAgent(kubeConfig, "leader-election"))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
eventClient, err := clientset.NewForConfig(kubeConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return client, leaderElectionClient, eventClient.CoreV1(), nil
|
||||
}
|
||||
|
||||
// Run runs the SchedulerServer. This should never exit.
|
||||
func (s *SchedulerServer) Run(stop chan struct{}) error {
|
||||
// To help debugging, immediately log version
|
||||
glog.Infof("Version: %+v", version.Get())
|
||||
|
||||
// Build a scheduler config from the provided algorithm source.
|
||||
schedulerConfig, err := s.SchedulerConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create the scheduler.
|
||||
sched := scheduler.NewFromConfig(schedulerConfig)
|
||||
|
||||
// Prepare the event broadcaster.
|
||||
if !reflect.ValueOf(s.Broadcaster).IsNil() && !reflect.ValueOf(s.EventClient).IsNil() {
|
||||
s.Broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: s.EventClient.Events("")})
|
||||
}
|
||||
|
||||
// Start up the healthz server.
|
||||
if s.HealthzServer != nil {
|
||||
go wait.Until(func() {
|
||||
glog.Infof("starting healthz server on %v", s.HealthzServer.Addr)
|
||||
err := s.HealthzServer.ListenAndServe()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to start healthz server: %v", err))
|
||||
}
|
||||
}, 5*time.Second, stop)
|
||||
}
|
||||
|
||||
// Start up the metrics server.
|
||||
if s.MetricsServer != nil {
|
||||
go wait.Until(func() {
|
||||
glog.Infof("starting metrics server on %v", s.MetricsServer.Addr)
|
||||
err := s.MetricsServer.ListenAndServe()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to start metrics server: %v", err))
|
||||
}
|
||||
}, 5*time.Second, stop)
|
||||
}
|
||||
|
||||
// Start all informers.
|
||||
go s.PodInformer.Informer().Run(stop)
|
||||
s.InformerFactory.Start(stop)
|
||||
|
||||
// Wait for all caches to sync before scheduling.
|
||||
s.InformerFactory.WaitForCacheSync(stop)
|
||||
controller.WaitForCacheSync("scheduler", stop, s.PodInformer.Informer().HasSynced)
|
||||
|
||||
// Prepare a reusable run function.
|
||||
run := func(stopCh <-chan struct{}) {
|
||||
sched.Run()
|
||||
<-stopCh
|
||||
}
|
||||
|
||||
// If leader election is enabled, run via LeaderElector until done and exit.
|
||||
if s.LeaderElection != nil {
|
||||
s.LeaderElection.Callbacks = leaderelection.LeaderCallbacks{
|
||||
OnStartedLeading: run,
|
||||
OnStoppedLeading: func() {
|
||||
utilruntime.HandleError(fmt.Errorf("lost master"))
|
||||
},
|
||||
}
|
||||
leaderElector, err := leaderelection.NewLeaderElector(*s.LeaderElection)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't create leader elector: %v", err)
|
||||
}
|
||||
|
||||
leaderElector.Run()
|
||||
|
||||
return fmt.Errorf("lost lease")
|
||||
}
|
||||
|
||||
// Leader election is disabled, so run inline until done.
|
||||
run(stop)
|
||||
return fmt.Errorf("finished without leader elect")
|
||||
}
|
||||
|
||||
// SchedulerConfig creates the scheduler configuration. This is exposed for use
|
||||
// by tests.
|
||||
func (s *SchedulerServer) SchedulerConfig() (*scheduler.Config, error) {
|
||||
var storageClassInformer storageinformers.StorageClassInformer
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
storageClassInformer = s.InformerFactory.Storage().V1().StorageClasses()
|
||||
}
|
||||
|
||||
// Set up the configurator which can create schedulers from configs.
|
||||
configurator := factory.NewConfigFactory(
|
||||
s.SchedulerName,
|
||||
s.Client,
|
||||
s.InformerFactory.Core().V1().Nodes(),
|
||||
s.PodInformer,
|
||||
s.InformerFactory.Core().V1().PersistentVolumes(),
|
||||
s.InformerFactory.Core().V1().PersistentVolumeClaims(),
|
||||
s.InformerFactory.Core().V1().ReplicationControllers(),
|
||||
s.InformerFactory.Extensions().V1beta1().ReplicaSets(),
|
||||
s.InformerFactory.Apps().V1beta1().StatefulSets(),
|
||||
s.InformerFactory.Core().V1().Services(),
|
||||
s.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(),
|
||||
storageClassInformer,
|
||||
s.HardPodAffinitySymmetricWeight,
|
||||
utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache),
|
||||
)
|
||||
|
||||
source := s.AlgorithmSource
|
||||
var config *scheduler.Config
|
||||
switch {
|
||||
case source.Provider != nil:
|
||||
// Create the config from a named algorithm provider.
|
||||
sc, err := configurator.CreateFromProvider(*source.Provider)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create scheduler using provider %q: %v", *source.Provider, err)
|
||||
}
|
||||
config = sc
|
||||
case source.Policy != nil:
|
||||
// Create the config from a user specified policy source.
|
||||
policy := &schedulerapi.Policy{}
|
||||
switch {
|
||||
case source.Policy.File != nil:
|
||||
// Use a policy serialized in a file.
|
||||
policyFile := source.Policy.File.Path
|
||||
_, err := os.Stat(policyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("missing policy config file %s", policyFile)
|
||||
}
|
||||
data, err := ioutil.ReadFile(policyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't read policy config: %v", err)
|
||||
}
|
||||
err = runtime.DecodeInto(latestschedulerapi.Codec, []byte(data), policy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid policy: %v", err)
|
||||
}
|
||||
case source.Policy.ConfigMap != nil:
|
||||
// Use a policy serialized in a config map value.
|
||||
policyRef := source.Policy.ConfigMap
|
||||
policyConfigMap, err := s.Client.CoreV1().ConfigMaps(policyRef.Namespace).Get(policyRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't get policy config map %s/%s: %v", policyRef.Namespace, policyRef.Name, err)
|
||||
}
|
||||
data, found := policyConfigMap.Data[componentconfig.SchedulerPolicyConfigMapKey]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("missing policy config map value at key %q", componentconfig.SchedulerPolicyConfigMapKey)
|
||||
}
|
||||
err = runtime.DecodeInto(latestschedulerapi.Codec, []byte(data), policy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid policy: %v", err)
|
||||
}
|
||||
}
|
||||
sc, err := configurator.CreateFromConfig(*policy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't create scheduler from policy: %v", err)
|
||||
}
|
||||
config = sc
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported algorithm source: %v", source)
|
||||
}
|
||||
// Additional tweaks to the config produced by the configurator.
|
||||
config.Recorder = s.Recorder
|
||||
return config, nil
|
||||
}
|
47
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/scheduler.go
generated
vendored
Normal file
47
vendor/k8s.io/kubernetes/plugin/cmd/kube-scheduler/scheduler.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
goflag "flag"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
utilflag "k8s.io/apiserver/pkg/util/flag"
|
||||
"k8s.io/apiserver/pkg/util/logs"
|
||||
_ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration
|
||||
_ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration
|
||||
"k8s.io/kubernetes/plugin/cmd/kube-scheduler/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
command := app.NewSchedulerCommand()
|
||||
|
||||
// TODO: once we switch everything over to Cobra commands, we can go back to calling
|
||||
// utilflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the
|
||||
// normalize func and add the go flag set by hand.
|
||||
pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc)
|
||||
pflag.CommandLine.AddGoFlagSet(goflag.CommandLine)
|
||||
// utilflag.InitFlags()
|
||||
logs.InitLogs()
|
||||
defer logs.FlushLogs()
|
||||
|
||||
if err := command.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
6
vendor/k8s.io/kubernetes/plugin/pkg/admission/OWNERS
generated
vendored
Normal file
6
vendor/k8s.io/kubernetes/plugin/pkg/admission/OWNERS
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
approvers:
|
||||
- derekwaynecarr
|
||||
- deads2k
|
||||
reviewers:
|
||||
- derekwaynecarr
|
||||
- deads2k
|
38
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/BUILD
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/admit",
|
||||
deps = ["//vendor/k8s.io/apiserver/pkg/admission:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/admit",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core: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"],
|
||||
)
|
58
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission.go
generated
vendored
Normal file
58
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/*
|
||||
Copyright 2014 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 admit
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("AlwaysAdmit", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysAdmit(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// AlwaysAdmit is an implementation of admission.Interface which always says yes to an admit request.
|
||||
// It is useful in tests and when using kubernetes in an open manner.
|
||||
type AlwaysAdmit struct{}
|
||||
|
||||
var _ admission.MutationInterface = AlwaysAdmit{}
|
||||
var _ admission.ValidationInterface = AlwaysAdmit{}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (AlwaysAdmit) Admit(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate.
|
||||
func (AlwaysAdmit) Validate(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
func (AlwaysAdmit) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewAlwaysAdmit creates a new always admit admission handler
|
||||
func NewAlwaysAdmit() *AlwaysAdmit {
|
||||
return new(AlwaysAdmit)
|
||||
}
|
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission_test.go
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/admit/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
Copyright 2014 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 admit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
func TestAdmissionNonNilAttribute(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
err := handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "namespace", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmissionNilAttribute(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
err := handler.Admit(nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
tests := []admission.Operation{admission.Create, admission.Connect, admission.Update, admission.Delete}
|
||||
|
||||
for _, test := range tests {
|
||||
if !handler.Handles(test) {
|
||||
t.Errorf("Expected handling all operations, including: %v", test)
|
||||
}
|
||||
}
|
||||
}
|
45
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/BUILD
generated
vendored
Normal file
45
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/BUILD
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime: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"],
|
||||
)
|
121
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
generated
vendored
Normal file
121
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 alwayspullimages contains an admission controller that modifies every new Pod to force
|
||||
// the image pull policy to Always. This is useful in a multitenant cluster so that users can be
|
||||
// assured that their private images can only be used by those who have the credentials to pull
|
||||
// them. Without this admission controller, once an image has been pulled to a node, any pod from
|
||||
// any user can use it simply by knowing the image's name (assuming the Pod is scheduled onto the
|
||||
// right node), without any authorization check against the image. With this admission controller
|
||||
// enabled, images are always pulled prior to starting containers, which means valid credentials are
|
||||
// required.
|
||||
package alwayspullimages
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("AlwaysPullImages", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysPullImages(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// AlwaysPullImages is an implementation of admission.Interface.
|
||||
// It looks at all new pods and overrides each container's image pull policy to Always.
|
||||
type AlwaysPullImages struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &AlwaysPullImages{}
|
||||
var _ admission.ValidationInterface = &AlwaysPullImages{}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (a *AlwaysPullImages) Admit(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if shouldIgnore(attributes) {
|
||||
return nil
|
||||
}
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
pod.Spec.InitContainers[i].ImagePullPolicy = api.PullAlways
|
||||
}
|
||||
|
||||
for i := range pod.Spec.Containers {
|
||||
pod.Spec.Containers[i].ImagePullPolicy = api.PullAlways
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes sure that all containers are set to always pull images
|
||||
func (*AlwaysPullImages) Validate(attributes admission.Attributes) (err error) {
|
||||
if shouldIgnore(attributes) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
if pod.Spec.InitContainers[i].ImagePullPolicy != api.PullAlways {
|
||||
return admission.NewForbidden(attributes,
|
||||
field.NotSupported(field.NewPath("spec", "initContainers").Index(i).Child("imagePullPolicy"),
|
||||
pod.Spec.InitContainers[i].ImagePullPolicy, []string{string(api.PullAlways)},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.Containers {
|
||||
if pod.Spec.Containers[i].ImagePullPolicy != api.PullAlways {
|
||||
return admission.NewForbidden(attributes,
|
||||
field.NotSupported(field.NewPath("spec", "containers").Index(i).Child("imagePullPolicy"),
|
||||
pod.Spec.Containers[i].ImagePullPolicy, []string{string(api.PullAlways)},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldIgnore(attributes admission.Attributes) bool {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewAlwaysPullImages creates a new always pull images admission control handler
|
||||
func NewAlwaysPullImages() *AlwaysPullImages {
|
||||
return &AlwaysPullImages{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
160
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission_test.go
generated
vendored
Normal file
160
vendor/k8s.io/kubernetes/plugin/pkg/admission/alwayspullimages/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
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 alwayspullimages
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
// TestAdmission verifies all create requests for pods result in every container's image pull policy
|
||||
// set to Always
|
||||
func TestAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
handler := &AlwaysPullImages{}
|
||||
pod := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{Name: "init1", Image: "image"},
|
||||
{Name: "init2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
for _, c := range pod.Spec.InitContainers {
|
||||
if c.ImagePullPolicy != api.PullAlways {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.ImagePullPolicy != api.PullAlways {
|
||||
t.Errorf("Container %v: expected pull always, got %v", c, c.ImagePullPolicy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
namespace := "test"
|
||||
handler := &AlwaysPullImages{}
|
||||
pod := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
InitContainers: []api.Container{
|
||||
{Name: "init1", Image: "image"},
|
||||
{Name: "init2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "init3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "init4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
{Name: "ctr3", Image: "image", ImagePullPolicy: api.PullIfNotPresent},
|
||||
{Name: "ctr4", Image: "image", ImagePullPolicy: api.PullAlways},
|
||||
},
|
||||
},
|
||||
}
|
||||
expectedError := `pods "123" is forbidden: spec.initContainers[0].imagePullPolicy: Unsupported value: "": supported values: "Always"`
|
||||
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Fatal("missing expected error")
|
||||
}
|
||||
if err.Error() != expectedError {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOtherResources ensures that this admission controller is a no-op for other resources,
|
||||
// subresources, and non-pods.
|
||||
func TestOtherResources(t *testing.T) {
|
||||
namespace := "testnamespace"
|
||||
name := "testname"
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr2", Image: "image", ImagePullPolicy: api.PullNever},
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kind string
|
||||
resource string
|
||||
subresource string
|
||||
object runtime.Object
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "non-pod resource",
|
||||
kind: "Foo",
|
||||
resource: "foos",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "pod subresource",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
subresource: "exec",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "non-pod object",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
object: &api.Service{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
handler := &AlwaysPullImages{}
|
||||
|
||||
err := handler.Admit(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: unexpected nil error", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if e, a := api.PullNever, pod.Spec.Containers[0].ImagePullPolicy; e != a {
|
||||
t.Errorf("%s: image pull policy was changed to %s", tc.name, a)
|
||||
}
|
||||
}
|
||||
}
|
49
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/BUILD
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/BUILD
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
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",
|
||||
"doc.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime: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"],
|
||||
)
|
78
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission.go
generated
vendored
Normal file
78
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission.go
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
Copyright 2016 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 antiaffinity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("LimitPodHardAntiAffinityTopology", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewInterPodAntiAffinity(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin contains the client used by the admission controller
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
// NewInterPodAntiAffinity creates a new instance of the LimitPodHardAntiAffinityTopology admission controller
|
||||
func NewInterPodAntiAffinity() *Plugin {
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate will deny any pod that defines AntiAffinity topology key other than kubeletapis.LabelHostname i.e. "kubernetes.io/hostname"
|
||||
// in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution.
|
||||
func (p *Plugin) Validate(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
affinity := pod.Spec.Affinity
|
||||
if affinity != nil && affinity.PodAntiAffinity != nil {
|
||||
var podAntiAffinityTerms []api.PodAffinityTerm
|
||||
if len(affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 {
|
||||
podAntiAffinityTerms = affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
|
||||
}
|
||||
// TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution.
|
||||
//if len(affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution) != 0 {
|
||||
// podAntiAffinityTerms = append(podAntiAffinityTerms, affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution...)
|
||||
//}
|
||||
for _, v := range podAntiAffinityTerms {
|
||||
if v.TopologyKey != kubeletapis.LabelHostname {
|
||||
return apierrors.NewForbidden(attributes.GetResource().GroupResource(), pod.Name, fmt.Errorf("affinity.PodAntiAffinity.RequiredDuringScheduling has TopologyKey %v but only key %v is allowed", v.TopologyKey, kubeletapis.LabelHostname))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
284
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission_test.go
generated
vendored
Normal file
284
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
/*
|
||||
Copyright 2016 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 antiaffinity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
)
|
||||
|
||||
// ensures the hard PodAntiAffinity is denied if it defines TopologyKey other than kubernetes.io/hostname.
|
||||
// TODO: Add test case "invalid topologyKey in requiredDuringSchedulingRequiredDuringExecution then admission fails"
|
||||
// after RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||
func TestInterPodAffinityAdmission(t *testing.T) {
|
||||
handler := NewInterPodAntiAffinity()
|
||||
pod := api.Pod{
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
tests := []struct {
|
||||
affinity *api.Affinity
|
||||
errorExpected bool
|
||||
}{
|
||||
// empty affinity its success.
|
||||
{
|
||||
affinity: &api.Affinity{},
|
||||
errorExpected: false,
|
||||
},
|
||||
// what ever topologyKey in preferredDuringSchedulingIgnoredDuringExecution, the admission should success.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
|
||||
{
|
||||
Weight: 5,
|
||||
PodAffinityTerm: api.PodAffinityTerm{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: "az",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution,
|
||||
// plus any topologyKey in preferredDuringSchedulingIgnoredDuringExecution, then admission success.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
PreferredDuringSchedulingIgnoredDuringExecution: []api.WeightedPodAffinityTerm{
|
||||
{
|
||||
Weight: 5,
|
||||
PodAffinityTerm: api.PodAffinityTerm{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: "az",
|
||||
},
|
||||
},
|
||||
},
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// valid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission success.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
// invalid topologyKey in requiredDuringSchedulingIgnoredDuringExecution then admission fails.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: " zone ",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
// list of requiredDuringSchedulingIgnoredDuringExecution middle element topologyKey is not valid.
|
||||
{
|
||||
affinity: &api.Affinity{
|
||||
PodAntiAffinity: &api.PodAntiAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: []api.PodAffinityTerm{
|
||||
{
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
}, {
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: " zone ",
|
||||
}, {
|
||||
LabelSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
TopologyKey: kubeletapis.LabelHostname,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
pod.Spec.Affinity = test.affinity
|
||||
err := handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
|
||||
if test.errorExpected && err == nil {
|
||||
t.Errorf("Expected error for Anti Affinity %+v but did not get an error", test.affinity)
|
||||
}
|
||||
|
||||
if !test.errorExpected && err != nil {
|
||||
t.Errorf("Unexpected error %v for AntiAffinity %+v", err, test.affinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewInterPodAntiAffinity()
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Update: true,
|
||||
admission.Create: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := handler.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOtherResources ensures that this admission controller is a no-op for other resources,
|
||||
// subresources, and non-pods.
|
||||
func TestOtherResources(t *testing.T) {
|
||||
namespace := "testnamespace"
|
||||
name := "testname"
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kind string
|
||||
resource string
|
||||
subresource string
|
||||
object runtime.Object
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "non-pod resource",
|
||||
kind: "Foo",
|
||||
resource: "foos",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "pod subresource",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
subresource: "eviction",
|
||||
object: pod,
|
||||
},
|
||||
{
|
||||
name: "non-pod object",
|
||||
kind: "Pod",
|
||||
resource: "pods",
|
||||
object: &api.Service{},
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
handler := &Plugin{}
|
||||
|
||||
err := handler.Validate(admission.NewAttributesRecord(tc.object, nil, api.Kind(tc.kind).WithVersion("version"), namespace, name, api.Resource(tc.resource).WithVersion("version"), tc.subresource, admission.Create, nil))
|
||||
|
||||
if tc.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: unexpected nil error", tc.name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", tc.name, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
28
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/doc.go
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/plugin/pkg/admission/antiaffinity/doc.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// LimitPodHardAntiAffinityTopology admission controller rejects any pod
|
||||
// that specifies "hard" (RequiredDuringScheduling) anti-affinity
|
||||
// with a TopologyKey other than kubeletapis.LabelHostname.
|
||||
// Because anti-affinity is symmetric, without this admission controller,
|
||||
// a user could maliciously or accidentally specify that their pod (once it has scheduled)
|
||||
// should block other pods from scheduling into the same zone or some other large topology,
|
||||
// essentially DoSing the cluster.
|
||||
// In the future we will address this problem more fully by using quota and priority,
|
||||
// but for now this admission controller provides a simple protection,
|
||||
// on the assumption that the only legitimate use of hard pod anti-affinity
|
||||
// is to exclude other pods from the same node.
|
||||
package antiaffinity // import "k8s.io/kubernetes/plugin/pkg/admission/antiaffinity"
|
46
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/BUILD
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/BUILD
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//plugin/pkg/scheduler/algorithm:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//plugin/pkg/scheduler/algorithm:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors: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"],
|
||||
)
|
122
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission.go
generated
vendored
Normal file
122
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
/*
|
||||
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 defaulttolerationseconds
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultNotReadyTolerationSeconds = flag.Int64("default-not-ready-toleration-seconds", 300,
|
||||
"Indicates the tolerationSeconds of the toleration for notReady:NoExecute"+
|
||||
" that is added by default to every pod that does not already have such a toleration.")
|
||||
|
||||
defaultUnreachableTolerationSeconds = flag.Int64("default-unreachable-toleration-seconds", 300,
|
||||
"Indicates the tolerationSeconds of the toleration for unreachable:NoExecute"+
|
||||
" that is added by default to every pod that does not already have such a toleration.")
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("DefaultTolerationSeconds", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewDefaultTolerationSeconds(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin contains the client used by the admission controller
|
||||
// It will add default tolerations for every pod
|
||||
// that tolerate taints `notReady:NoExecute` and `unreachable:NoExecute`,
|
||||
// with tolerationSeconds of 300s.
|
||||
// If the pod already specifies a toleration for taint `notReady:NoExecute`
|
||||
// or `unreachable:NoExecute`, the plugin won't touch it.
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
|
||||
// NewDefaultTolerationSeconds creates a new instance of the DefaultTolerationSeconds admission controller
|
||||
func NewDefaultTolerationSeconds() *Plugin {
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (p *Plugin) Admit(attributes admission.Attributes) (err error) {
|
||||
if attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(attributes.GetSubresource()) > 0 {
|
||||
// only run the checks below on pods proper and not subresources
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return errors.NewBadRequest(fmt.Sprintf("expected *api.Pod but got %T", attributes.GetObject()))
|
||||
}
|
||||
|
||||
tolerations := pod.Spec.Tolerations
|
||||
|
||||
toleratesNodeNotReady := false
|
||||
toleratesNodeUnreachable := false
|
||||
for _, toleration := range tolerations {
|
||||
if (toleration.Key == algorithm.TaintNodeNotReady || len(toleration.Key) == 0) &&
|
||||
(toleration.Effect == api.TaintEffectNoExecute || len(toleration.Effect) == 0) {
|
||||
toleratesNodeNotReady = true
|
||||
}
|
||||
|
||||
if (toleration.Key == algorithm.TaintNodeUnreachable || len(toleration.Key) == 0) &&
|
||||
(toleration.Effect == api.TaintEffectNoExecute || len(toleration.Effect) == 0) {
|
||||
toleratesNodeUnreachable = true
|
||||
}
|
||||
}
|
||||
|
||||
// no change is required, return immediately
|
||||
if toleratesNodeNotReady && toleratesNodeUnreachable {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !toleratesNodeNotReady {
|
||||
helper.AddOrUpdateTolerationInPod(pod, &api.Toleration{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: defaultNotReadyTolerationSeconds,
|
||||
})
|
||||
}
|
||||
|
||||
if !toleratesNodeUnreachable {
|
||||
helper.AddOrUpdateTolerationInPod(pod, &api.Toleration{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: defaultUnreachableTolerationSeconds,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
423
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission_test.go
generated
vendored
Normal file
423
vendor/k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
/*
|
||||
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 defaulttolerationseconds
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
"k8s.io/kubernetes/plugin/pkg/scheduler/algorithm"
|
||||
)
|
||||
|
||||
func TestForgivenessAdmission(t *testing.T) {
|
||||
var defaultTolerationSeconds int64 = 300
|
||||
|
||||
genTolerationSeconds := func(s int64) *int64 {
|
||||
return &s
|
||||
}
|
||||
|
||||
handler := NewDefaultTolerationSeconds()
|
||||
// NOTE: for anyone who want to modify this test, the order of tolerations matters!
|
||||
tests := []struct {
|
||||
description string
|
||||
requestedPod api.Pod
|
||||
expectedPod api.Pod
|
||||
}{
|
||||
{
|
||||
description: "pod has no tolerations, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has alpha tolerations, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" +
|
||||
", the alpha tolerations will not be touched",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has alpha not-ready toleration, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" +
|
||||
", the alpha tolerations will not be touched",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has alpha unreachable toleration, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`" +
|
||||
", the alpha tolerations will not be touched",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.DeprecatedTaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has tolerations, but none is for taint `not-ready:NoExecute` or `unreachable:NoExecute`, expect add tolerations for `not-ready:NoExecute` and `unreachable:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: api.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: api.TaintEffectNoSchedule,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: api.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: api.TaintEffectNoSchedule,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified a toleration for taint `not-ready:NoExecute`, expect add toleration for `unreachable:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified a toleration for taint `unreachable:NoExecute`, expect add toleration for `not-ready:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: &defaultTolerationSeconds,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified tolerations for both `not-ready:NoExecute` and `unreachable:NoExecute`, expect no change",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod specified toleration for taint `unreachable`, expect add toleration for `not-ready:NoExecute`",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Key: algorithm.TaintNodeUnreachable,
|
||||
Operator: api.TolerationOpExists,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
{
|
||||
Key: algorithm.TaintNodeNotReady,
|
||||
Operator: api.TolerationOpExists,
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
TolerationSeconds: genTolerationSeconds(300),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod has wildcard toleration for all kind of taints, expect no change",
|
||||
requestedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{Operator: api.TolerationOpExists, TolerationSeconds: genTolerationSeconds(700)},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
Tolerations: []api.Toleration{
|
||||
{
|
||||
Operator: api.TolerationOpExists,
|
||||
TolerationSeconds: genTolerationSeconds(700),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
err := handler.Admit(admission.NewAttributesRecord(&test.requestedPod, nil, api.Kind("Pod").WithVersion("version"), "foo", "name", api.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
if err != nil {
|
||||
t.Errorf("[%s]: unexpected error %v for pod %+v", test.description, err, test.requestedPod)
|
||||
}
|
||||
|
||||
if !helper.Semantic.DeepEqual(test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) {
|
||||
t.Errorf("[%s]: expected %#v got %#v", test.description, test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewDefaultTolerationSeconds()
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Update: true,
|
||||
admission.Create: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := handler.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
38
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/BUILD
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/deny",
|
||||
deps = ["//vendor/k8s.io/apiserver/pkg/admission:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/deny",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core: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"],
|
||||
)
|
59
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission.go
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2014 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 deny
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("AlwaysDeny", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysDeny(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// AlwaysDeny is an implementation of admission.Interface which always says no to an admission request.
|
||||
// It is useful in unit tests to force an operation to be forbidden.
|
||||
type AlwaysDeny struct{}
|
||||
|
||||
var _ admission.MutationInterface = AlwaysDeny{}
|
||||
var _ admission.ValidationInterface = AlwaysDeny{}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (AlwaysDeny) Admit(a admission.Attributes) (err error) {
|
||||
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate.
|
||||
func (AlwaysDeny) Validate(a admission.Attributes) (err error) {
|
||||
return admission.NewForbidden(a, errors.New("Admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
func (AlwaysDeny) Handles(operation admission.Operation) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// NewAlwaysDeny creates an always deny admission handler
|
||||
func NewAlwaysDeny() *AlwaysDeny {
|
||||
return new(AlwaysDeny)
|
||||
}
|
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission_test.go
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/deny/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2014 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 deny
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewAlwaysDeny()
|
||||
err := handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("kind").WithVersion("version"), "namespace", "name", api.Resource("resource").WithVersion("version"), "subresource", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Error("Expected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
handler := NewAlwaysDeny()
|
||||
tests := []admission.Operation{admission.Create, admission.Connect, admission.Update, admission.Delete}
|
||||
|
||||
for _, test := range tests {
|
||||
if !handler.Handles(test) {
|
||||
t.Errorf("Expected handling all operations, including: %v", test)
|
||||
}
|
||||
}
|
||||
}
|
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/BUILD
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/BUILD
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"cache_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"admission.go",
|
||||
"cache.go",
|
||||
"clock.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
"limitenforcer.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/install:go_default_library",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:go_default_library",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation:go_default_library",
|
||||
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
94
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission.go
generated
vendored
Normal file
94
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("EventRateLimit",
|
||||
func(config io.Reader) (admission.Interface, error) {
|
||||
// load the configuration provided (if any)
|
||||
configuration, err := LoadConfiguration(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate the configuration (if any)
|
||||
if configuration != nil {
|
||||
if errs := validation.ValidateConfiguration(configuration); len(errs) != 0 {
|
||||
return nil, errs.ToAggregate()
|
||||
}
|
||||
}
|
||||
return newEventRateLimit(configuration, realClock{})
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin implements an admission controller that can enforce event rate limits
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
// limitEnforcers is the collection of limit enforcers. There is one limit enforcer for each
|
||||
// active limit type. As there are 4 limit types, the length of the array will be at most 4.
|
||||
// The array is read-only after construction.
|
||||
limitEnforcers []*limitEnforcer
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
// newEventRateLimit configures an admission controller that can enforce event rate limits
|
||||
func newEventRateLimit(config *eventratelimitapi.Configuration, clock flowcontrol.Clock) (*Plugin, error) {
|
||||
limitEnforcers := make([]*limitEnforcer, 0, len(config.Limits))
|
||||
for _, limitConfig := range config.Limits {
|
||||
enforcer, err := newLimitEnforcer(limitConfig, clock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
limitEnforcers = append(limitEnforcers, enforcer)
|
||||
}
|
||||
|
||||
eventRateLimitAdmission := &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
limitEnforcers: limitEnforcers,
|
||||
}
|
||||
|
||||
return eventRateLimitAdmission, nil
|
||||
}
|
||||
|
||||
// Validate makes admission decisions while enforcing event rate limits
|
||||
func (a *Plugin) Validate(attr admission.Attributes) (err error) {
|
||||
// ignore all operations that do not correspond to an Event kind
|
||||
if attr.GetKind().GroupKind() != api.Kind("Event") {
|
||||
return nil
|
||||
}
|
||||
|
||||
var rejectionError error
|
||||
// give each limit enforcer a chance to reject the event
|
||||
for _, enforcer := range a.limitEnforcers {
|
||||
if err := enforcer.accept(attr); err != nil {
|
||||
rejectionError = err
|
||||
}
|
||||
}
|
||||
|
||||
return rejectionError
|
||||
}
|
502
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission_test.go
generated
vendored
Normal file
502
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,502 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
)
|
||||
|
||||
const (
|
||||
qps = 1
|
||||
eventKind = "Event"
|
||||
nonEventKind = "NonEvent"
|
||||
)
|
||||
|
||||
// attributesForRequest generates the admission.Attributes that for the specified request
|
||||
func attributesForRequest(rq request) admission.Attributes {
|
||||
return admission.NewAttributesRecord(
|
||||
rq.event,
|
||||
nil,
|
||||
api.Kind(rq.kind).WithVersion("version"),
|
||||
rq.namespace,
|
||||
"name",
|
||||
api.Resource("resource").WithVersion("version"),
|
||||
"",
|
||||
admission.Create,
|
||||
&user.DefaultInfo{Name: rq.username})
|
||||
}
|
||||
|
||||
type request struct {
|
||||
kind string
|
||||
namespace string
|
||||
username string
|
||||
event *api.Event
|
||||
delay time.Duration
|
||||
accepted bool
|
||||
}
|
||||
|
||||
func newRequest(kind string) request {
|
||||
return request{
|
||||
kind: kind,
|
||||
accepted: true,
|
||||
}
|
||||
}
|
||||
|
||||
func newEventRequest() request {
|
||||
return newRequest(eventKind)
|
||||
}
|
||||
|
||||
func newNonEventRequest() request {
|
||||
return newRequest(nonEventKind)
|
||||
}
|
||||
|
||||
func (r request) withNamespace(namespace string) request {
|
||||
r.namespace = namespace
|
||||
return r
|
||||
}
|
||||
|
||||
func (r request) withEvent(event *api.Event) request {
|
||||
r.event = event
|
||||
return r
|
||||
}
|
||||
|
||||
func (r request) withEventComponent(component string) request {
|
||||
return r.withEvent(&api.Event{
|
||||
Source: api.EventSource{
|
||||
Component: component,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (r request) withUser(name string) request {
|
||||
r.username = name
|
||||
return r
|
||||
}
|
||||
|
||||
func (r request) blocked() request {
|
||||
r.accepted = false
|
||||
return r
|
||||
}
|
||||
|
||||
// withDelay will adjust the clock to simulate the specified delay, in seconds
|
||||
func (r request) withDelay(delayInSeconds int) request {
|
||||
r.delay = time.Duration(delayInSeconds) * time.Second
|
||||
return r
|
||||
}
|
||||
|
||||
// createSourceAndObjectKeyInclusionRequests creates a series of requests that can be used
|
||||
// to test that a particular part of the event is included in the source+object key
|
||||
func createSourceAndObjectKeyInclusionRequests(eventFactory func(label string) *api.Event) []request {
|
||||
return []request{
|
||||
newEventRequest().withEvent(eventFactory("A")),
|
||||
newEventRequest().withEvent(eventFactory("A")).blocked(),
|
||||
newEventRequest().withEvent(eventFactory("B")),
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventRateLimiting(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
serverBurst int32
|
||||
namespaceBurst int32
|
||||
namespaceCacheSize int32
|
||||
sourceAndObjectBurst int32
|
||||
sourceAndObjectCacheSize int32
|
||||
userBurst int32
|
||||
userCacheSize int32
|
||||
requests []request
|
||||
}{
|
||||
{
|
||||
name: "event not blocked when tokens available",
|
||||
serverBurst: 3,
|
||||
requests: []request{
|
||||
newEventRequest(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-event not blocked",
|
||||
serverBurst: 3,
|
||||
requests: []request{
|
||||
newNonEventRequest(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event blocked after tokens exhausted",
|
||||
serverBurst: 3,
|
||||
requests: []request{
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newEventRequest().blocked(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-event not blocked after tokens exhausted",
|
||||
serverBurst: 3,
|
||||
requests: []request{
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newNonEventRequest(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-events should not count against limit",
|
||||
serverBurst: 3,
|
||||
requests: []request{
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newNonEventRequest(),
|
||||
newEventRequest(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event accepted after token refill",
|
||||
serverBurst: 3,
|
||||
requests: []request{
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newEventRequest(),
|
||||
newEventRequest().blocked(),
|
||||
newEventRequest().withDelay(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event blocked by namespace limits",
|
||||
serverBurst: 100,
|
||||
namespaceBurst: 3,
|
||||
namespaceCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A").blocked(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event from other namespace not blocked",
|
||||
serverBurst: 100,
|
||||
namespaceBurst: 3,
|
||||
namespaceCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "events from other namespaces should not count against limit",
|
||||
serverBurst: 100,
|
||||
namespaceBurst: 3,
|
||||
namespaceCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("B"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event accepted after namespace token refill",
|
||||
serverBurst: 100,
|
||||
namespaceBurst: 3,
|
||||
namespaceCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A").blocked(),
|
||||
newEventRequest().withNamespace("A").withDelay(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event from other namespaces should not clear namespace limits",
|
||||
serverBurst: 100,
|
||||
namespaceBurst: 3,
|
||||
namespaceCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("B"),
|
||||
newEventRequest().withNamespace("A").blocked(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "namespace limits from lru namespace should clear when cache size exceeded",
|
||||
serverBurst: 100,
|
||||
namespaceBurst: 3,
|
||||
namespaceCacheSize: 2,
|
||||
requests: []request{
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("B"),
|
||||
newEventRequest().withNamespace("B"),
|
||||
newEventRequest().withNamespace("B"),
|
||||
newEventRequest().withNamespace("A"),
|
||||
newEventRequest().withNamespace("B").blocked(),
|
||||
newEventRequest().withNamespace("A").blocked(),
|
||||
// This should clear out namespace B from the lru cache
|
||||
newEventRequest().withNamespace("C"),
|
||||
newEventRequest().withNamespace("A").blocked(),
|
||||
newEventRequest().withNamespace("B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event blocked by source+object limits",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 3,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A").blocked(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event from other source+object not blocked",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 3,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "events from other source+object should not count against limit",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 3,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event accepted after source+object token refill",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 3,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A").blocked(),
|
||||
newEventRequest().withEventComponent("A").withDelay(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event from other source+object should not clear source+object limits",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 3,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
newEventRequest().withEventComponent("A").blocked(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "source+object limits from lru source+object should clear when cache size exceeded",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 3,
|
||||
sourceAndObjectCacheSize: 2,
|
||||
requests: []request{
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
newEventRequest().withEventComponent("A"),
|
||||
newEventRequest().withEventComponent("B").blocked(),
|
||||
newEventRequest().withEventComponent("A").blocked(),
|
||||
// This should clear out component B from the lru cache
|
||||
newEventRequest().withEventComponent("C"),
|
||||
newEventRequest().withEventComponent("A").blocked(),
|
||||
newEventRequest().withEventComponent("B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "source host should be included in source+object key",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 1,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||
return &api.Event{Source: api.EventSource{Host: label}}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "involved object kind should be included in source+object key",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 1,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||
return &api.Event{InvolvedObject: api.ObjectReference{Kind: label}}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "involved object namespace should be included in source+object key",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 1,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||
return &api.Event{InvolvedObject: api.ObjectReference{Namespace: label}}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "involved object name should be included in source+object key",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 1,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||
return &api.Event{InvolvedObject: api.ObjectReference{Name: label}}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "involved object UID should be included in source+object key",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 1,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||
return &api.Event{InvolvedObject: api.ObjectReference{UID: types.UID(label)}}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "involved object APIVersion should be included in source+object key",
|
||||
serverBurst: 100,
|
||||
sourceAndObjectBurst: 1,
|
||||
sourceAndObjectCacheSize: 10,
|
||||
requests: createSourceAndObjectKeyInclusionRequests(func(label string) *api.Event {
|
||||
return &api.Event{InvolvedObject: api.ObjectReference{APIVersion: label}}
|
||||
}),
|
||||
},
|
||||
{
|
||||
name: "event blocked by user limits",
|
||||
userBurst: 3,
|
||||
userCacheSize: 10,
|
||||
requests: []request{
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("A").blocked(),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "event from other user not blocked",
|
||||
requests: []request{
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("B"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "events from other user should not count against limit",
|
||||
requests: []request{
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("A"),
|
||||
newEventRequest().withUser("B"),
|
||||
newEventRequest().withUser("A"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
clock := clock.NewFakeClock(time.Now())
|
||||
config := &eventratelimitapi.Configuration{}
|
||||
if tc.serverBurst > 0 {
|
||||
serverLimit := eventratelimitapi.Limit{
|
||||
Type: eventratelimitapi.ServerLimitType,
|
||||
QPS: qps,
|
||||
Burst: tc.serverBurst,
|
||||
}
|
||||
config.Limits = append(config.Limits, serverLimit)
|
||||
}
|
||||
if tc.namespaceBurst > 0 {
|
||||
namespaceLimit := eventratelimitapi.Limit{
|
||||
Type: eventratelimitapi.NamespaceLimitType,
|
||||
Burst: tc.namespaceBurst,
|
||||
QPS: qps,
|
||||
CacheSize: tc.namespaceCacheSize,
|
||||
}
|
||||
config.Limits = append(config.Limits, namespaceLimit)
|
||||
}
|
||||
if tc.userBurst > 0 {
|
||||
userLimit := eventratelimitapi.Limit{
|
||||
Type: eventratelimitapi.UserLimitType,
|
||||
Burst: tc.userBurst,
|
||||
QPS: qps,
|
||||
CacheSize: tc.userCacheSize,
|
||||
}
|
||||
config.Limits = append(config.Limits, userLimit)
|
||||
}
|
||||
if tc.sourceAndObjectBurst > 0 {
|
||||
sourceAndObjectLimit := eventratelimitapi.Limit{
|
||||
Type: eventratelimitapi.SourceAndObjectLimitType,
|
||||
Burst: tc.sourceAndObjectBurst,
|
||||
QPS: qps,
|
||||
CacheSize: tc.sourceAndObjectCacheSize,
|
||||
}
|
||||
config.Limits = append(config.Limits, sourceAndObjectLimit)
|
||||
}
|
||||
eventratelimit, err := newEventRateLimit(config, clock)
|
||||
if err != nil {
|
||||
t.Fatalf("%v: Could not create EventRateLimit: %v", tc.name, err)
|
||||
}
|
||||
|
||||
for rqIndex, rq := range tc.requests {
|
||||
if rq.delay > 0 {
|
||||
clock.Step(rq.delay)
|
||||
}
|
||||
attributes := attributesForRequest(rq)
|
||||
err = eventratelimit.Validate(attributes)
|
||||
if rq.accepted != (err == nil) {
|
||||
expectedAction := "admitted"
|
||||
if !rq.accepted {
|
||||
expectedAction = "blocked"
|
||||
}
|
||||
t.Fatalf("%v: Request %v should have been %v: %v", tc.name, rqIndex, expectedAction, err)
|
||||
}
|
||||
if err != nil {
|
||||
statusErr, ok := err.(*errors.StatusError)
|
||||
if ok && statusErr.ErrStatus.Code != errors.StatusTooManyRequests {
|
||||
t.Fatalf("%v: Request %v should yield a 429 response: %v", tc.name, rqIndex, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/BUILD
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"doc.go",
|
||||
"register.go",
|
||||
"types.go",
|
||||
"zz_generated.deepcopy.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit",
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/install:all-srcs",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:all-srcs",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
7
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/OWNERS
generated
vendored
Executable file
7
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/OWNERS
generated
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
reviewers:
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
approvers:
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- smarterclayton
|
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
|
||||
package eventratelimit // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
32
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/BUILD
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/BUILD
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["install.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install",
|
||||
deps = [
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/announced:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apimachinery/registered:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/install.go
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install/install.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 install installs the experimental API group, making it available as
|
||||
// an option to all of the API encoding/decoding machinery.
|
||||
package install
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
internalapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
versionedapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
|
||||
)
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) {
|
||||
if err := announced.NewGroupMetaFactory(
|
||||
&announced.GroupMetaFactoryArgs{
|
||||
GroupName: internalapi.GroupName,
|
||||
VersionPreferenceOrder: []string{versionedapi.SchemeGroupVersion.Version},
|
||||
AddInternalObjectsToScheme: internalapi.AddToScheme,
|
||||
},
|
||||
announced.VersionToSchemeFunc{
|
||||
versionedapi.SchemeGroupVersion.Version: versionedapi.AddToScheme,
|
||||
},
|
||||
).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/register.go
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/register.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "eventratelimit.admission.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
// TODO this will get cleaned up with the scheme types are fixed
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Configuration{},
|
||||
)
|
||||
return nil
|
||||
}
|
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/types.go
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/types.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// LimitType is the type of the limit (e.g., per-namespace)
|
||||
type LimitType string
|
||||
|
||||
const (
|
||||
// ServerLimitType is a type of limit where there is one bucket shared by
|
||||
// all of the event queries received by the API Server.
|
||||
ServerLimitType LimitType = "Server"
|
||||
// NamespaceLimitType is a type of limit where there is one bucket used by
|
||||
// each namespace
|
||||
NamespaceLimitType LimitType = "Namespace"
|
||||
// UserLimitType is a type of limit where there is one bucket used by each
|
||||
// user
|
||||
UserLimitType LimitType = "User"
|
||||
// SourceAndObjectLimitType is a type of limit where there is one bucket used
|
||||
// by each combination of source and involved object of the event.
|
||||
SourceAndObjectLimitType LimitType = "SourceAndObject"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Configuration provides configuration for the EventRateLimit admission
|
||||
// controller.
|
||||
type Configuration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// limits are the limits to place on event queries received.
|
||||
// Limits can be placed on events received server-wide, per namespace,
|
||||
// per user, and per source+object.
|
||||
// At least one limit is required.
|
||||
Limits []Limit `json:"limits"`
|
||||
}
|
||||
|
||||
// Limit is the configuration for a particular limit type
|
||||
type Limit struct {
|
||||
// type is the type of limit to which this configuration applies
|
||||
Type LimitType `json:"type"`
|
||||
|
||||
// qps is the number of event queries per second that are allowed for this
|
||||
// type of limit. The qps and burst fields are used together to determine if
|
||||
// a particular event query is accepted. The qps determines how many queries
|
||||
// are accepted once the burst amount of queries has been exhausted.
|
||||
QPS int32 `json:"qps"`
|
||||
|
||||
// burst is the burst number of event queries that are allowed for this type
|
||||
// of limit. The qps and burst fields are used together to determine if a
|
||||
// particular event query is accepted. The burst determines the maximum size
|
||||
// of the allowance granted for a particular bucket. For example, if the burst
|
||||
// is 10 and the qps is 3, then the admission control will accept 10 queries
|
||||
// before blocking any queries. Every second, 3 more queries will be allowed.
|
||||
// If some of that allowance is not used, then it will roll over to the next
|
||||
// second, until the maximum allowance of 10 is reached.
|
||||
Burst int32 `json:"burst"`
|
||||
|
||||
// cacheSize is the size of the LRU cache for this type of limit. If a bucket
|
||||
// is evicted from the cache, then the allowance for that bucket is reset. If
|
||||
// more queries are later received for an evicted bucket, then that bucket
|
||||
// will re-enter the cache with a clean slate, giving that bucket a full
|
||||
// allowance of burst queries.
|
||||
//
|
||||
// The default cache size is 4096.
|
||||
//
|
||||
// If limitType is 'server', then cacheSize is ignored.
|
||||
// +optional
|
||||
CacheSize int32 `json:"cacheSize,omitempty"`
|
||||
}
|
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/BUILD
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"defaults.go",
|
||||
"doc.go",
|
||||
"register.go",
|
||||
"types.go",
|
||||
"zz_generated.conversion.go",
|
||||
"zz_generated.deepcopy.go",
|
||||
"zz_generated.defaults.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1",
|
||||
deps = [
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
25
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/defaults.go
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/defaults.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import kruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
func addDefaultingFuncs(scheme *kruntime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
|
||||
func SetDefaults_Configuration(obj *Configuration) {}
|
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go
generated
vendored
Normal file
23
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/doc.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
// Package v1alpha1 is the v1alpha1 version of the API.
|
||||
// +groupName=eventratelimit.admission.k8s.io
|
||||
package v1alpha1 // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
|
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/register.go
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/register.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "eventratelimit.admission.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||
|
||||
var (
|
||||
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs)
|
||||
}
|
||||
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Configuration{},
|
||||
)
|
||||
return nil
|
||||
}
|
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/types.go
generated
vendored
Normal file
85
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/types.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// LimitType is the type of the limit (e.g., per-namespace)
|
||||
type LimitType string
|
||||
|
||||
const (
|
||||
// ServerLimitType is a type of limit where there is one bucket shared by
|
||||
// all of the event queries received by the API Server.
|
||||
ServerLimitType LimitType = "Server"
|
||||
// NamespaceLimitType is a type of limit where there is one bucket used by
|
||||
// each namespace
|
||||
NamespaceLimitType LimitType = "Namespace"
|
||||
// UserLimitType is a type of limit where there is one bucket used by each
|
||||
// user
|
||||
UserLimitType LimitType = "User"
|
||||
// SourceAndObjectLimitType is a type of limit where there is one bucket used
|
||||
// by each combination of source and involved object of the event.
|
||||
SourceAndObjectLimitType LimitType = "SourceAndObject"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Configuration provides configuration for the EventRateLimit admission
|
||||
// controller.
|
||||
type Configuration struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// limits are the limits to place on event queries received.
|
||||
// Limits can be placed on events received server-wide, per namespace,
|
||||
// per user, and per source+object.
|
||||
// At least one limit is required.
|
||||
Limits []Limit `json:"limits"`
|
||||
}
|
||||
|
||||
// Limit is the configuration for a particular limit type
|
||||
type Limit struct {
|
||||
// type is the type of limit to which this configuration applies
|
||||
Type LimitType `json:"type"`
|
||||
|
||||
// qps is the number of event queries per second that are allowed for this
|
||||
// type of limit. The qps and burst fields are used together to determine if
|
||||
// a particular event query is accepted. The qps determines how many queries
|
||||
// are accepted once the burst amount of queries has been exhausted.
|
||||
QPS int32 `json:"qps"`
|
||||
|
||||
// burst is the burst number of event queries that are allowed for this type
|
||||
// of limit. The qps and burst fields are used together to determine if a
|
||||
// particular event query is accepted. The burst determines the maximum size
|
||||
// of the allowance granted for a particular bucket. For example, if the burst
|
||||
// is 10 and the qps is 3, then the admission control will accept 10 queries
|
||||
// before blocking any queries. Every second, 3 more queries will be allowed.
|
||||
// If some of that allowance is not used, then it will roll over to the next
|
||||
// second, until the maximum allowance of 10 is reached.
|
||||
Burst int32 `json:"burst"`
|
||||
|
||||
// cacheSize is the size of the LRU cache for this type of limit. If a bucket
|
||||
// is evicted from the cache, then the allowance for that bucket is reset. If
|
||||
// more queries are later received for an evicted bucket, then that bucket
|
||||
// will re-enter the cache with a clean slate, giving that bucket a full
|
||||
// allowance of burst queries.
|
||||
//
|
||||
// The default cache size is 4096.
|
||||
//
|
||||
// If limitType is 'server', then cacheSize is ignored.
|
||||
// +optional
|
||||
CacheSize int32 `json:"cacheSize,omitempty"`
|
||||
}
|
89
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
89
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.conversion.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by conversion-gen. Do not edit it manually!
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
eventratelimit "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(scheme *runtime.Scheme) error {
|
||||
return scheme.AddGeneratedConversionFuncs(
|
||||
Convert_v1alpha1_Configuration_To_eventratelimit_Configuration,
|
||||
Convert_eventratelimit_Configuration_To_v1alpha1_Configuration,
|
||||
Convert_v1alpha1_Limit_To_eventratelimit_Limit,
|
||||
Convert_eventratelimit_Limit_To_v1alpha1_Limit,
|
||||
)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_Configuration_To_eventratelimit_Configuration(in *Configuration, out *eventratelimit.Configuration, s conversion.Scope) error {
|
||||
out.Limits = *(*[]eventratelimit.Limit)(unsafe.Pointer(&in.Limits))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_Configuration_To_eventratelimit_Configuration is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_Configuration_To_eventratelimit_Configuration(in *Configuration, out *eventratelimit.Configuration, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_Configuration_To_eventratelimit_Configuration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_eventratelimit_Configuration_To_v1alpha1_Configuration(in *eventratelimit.Configuration, out *Configuration, s conversion.Scope) error {
|
||||
out.Limits = *(*[]Limit)(unsafe.Pointer(&in.Limits))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_eventratelimit_Configuration_To_v1alpha1_Configuration is an autogenerated conversion function.
|
||||
func Convert_eventratelimit_Configuration_To_v1alpha1_Configuration(in *eventratelimit.Configuration, out *Configuration, s conversion.Scope) error {
|
||||
return autoConvert_eventratelimit_Configuration_To_v1alpha1_Configuration(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_Limit_To_eventratelimit_Limit(in *Limit, out *eventratelimit.Limit, s conversion.Scope) error {
|
||||
out.Type = eventratelimit.LimitType(in.Type)
|
||||
out.QPS = in.QPS
|
||||
out.Burst = in.Burst
|
||||
out.CacheSize = in.CacheSize
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_Limit_To_eventratelimit_Limit is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_Limit_To_eventratelimit_Limit(in *Limit, out *eventratelimit.Limit, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_Limit_To_eventratelimit_Limit(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_eventratelimit_Limit_To_v1alpha1_Limit(in *eventratelimit.Limit, out *Limit, s conversion.Scope) error {
|
||||
out.Type = LimitType(in.Type)
|
||||
out.QPS = in.QPS
|
||||
out.Burst = in.Burst
|
||||
out.CacheSize = in.CacheSize
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_eventratelimit_Limit_To_v1alpha1_Limit is an autogenerated conversion function.
|
||||
func Convert_eventratelimit_Limit_To_v1alpha1_Limit(in *eventratelimit.Limit, out *Limit, s conversion.Scope) error {
|
||||
return autoConvert_eventratelimit_Limit_To_v1alpha1_Limit(in, out, s)
|
||||
}
|
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Configuration) DeepCopyInto(out *Configuration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Limits != nil {
|
||||
in, out := &in.Limits, &out.Limits
|
||||
*out = make([]Limit, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration.
|
||||
func (in *Configuration) DeepCopy() *Configuration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Configuration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Configuration) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Limit) DeepCopyInto(out *Limit) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit.
|
||||
func (in *Limit) DeepCopy() *Limit {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Limit)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
37
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1/zz_generated.defaults.go
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by defaulter-gen. Do not edit it manually!
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
scheme.AddTypeDefaultingFunc(&Configuration{}, func(obj interface{}) { SetObjectDefaults_Configuration(obj.(*Configuration)) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetObjectDefaults_Configuration(in *Configuration) {
|
||||
SetDefaults_Configuration(in)
|
||||
}
|
38
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/BUILD
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["validation.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation",
|
||||
deps = [
|
||||
"//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["validation_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation",
|
||||
library = ":go_default_library",
|
||||
deps = ["//plugin/pkg/admission/eventratelimit/apis/eventratelimit:go_default_library"],
|
||||
)
|
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation.go
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation.go
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
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 validation
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
)
|
||||
|
||||
var limitTypes = map[eventratelimitapi.LimitType]bool{
|
||||
eventratelimitapi.ServerLimitType: true,
|
||||
eventratelimitapi.NamespaceLimitType: true,
|
||||
eventratelimitapi.UserLimitType: true,
|
||||
eventratelimitapi.SourceAndObjectLimitType: true,
|
||||
}
|
||||
|
||||
// ValidateConfiguration validates the configuration.
|
||||
func ValidateConfiguration(config *eventratelimitapi.Configuration) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
limitsPath := field.NewPath("limits")
|
||||
if len(config.Limits) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(limitsPath, config.Limits, "must not be empty"))
|
||||
}
|
||||
for i, limit := range config.Limits {
|
||||
idxPath := limitsPath.Index(i)
|
||||
if !limitTypes[limit.Type] {
|
||||
allowedValues := make([]string, len(limitTypes))
|
||||
i := 0
|
||||
for limitType := range limitTypes {
|
||||
allowedValues[i] = string(limitType)
|
||||
i++
|
||||
}
|
||||
allErrs = append(allErrs, field.NotSupported(idxPath.Child("type"), limit.Type, allowedValues))
|
||||
}
|
||||
if limit.Burst <= 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("burst"), limit.Burst, "must be positive"))
|
||||
}
|
||||
if limit.QPS <= 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("qps"), limit.QPS, "must be positive"))
|
||||
}
|
||||
if limit.Type != eventratelimitapi.ServerLimitType {
|
||||
if limit.CacheSize < 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("cacheSize"), limit.CacheSize, "must not be negative"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
192
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go
generated
vendored
Normal file
192
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation/validation_test.go
generated
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
/*
|
||||
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 validation
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
)
|
||||
|
||||
func TestValidateConfiguration(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
config eventratelimitapi.Configuration
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "valid server",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "Server",
|
||||
Burst: 5,
|
||||
QPS: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "valid namespace",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "Namespace",
|
||||
Burst: 10,
|
||||
QPS: 2,
|
||||
CacheSize: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "valid user",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "User",
|
||||
Burst: 10,
|
||||
QPS: 2,
|
||||
CacheSize: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "valid source+object",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "SourceAndObject",
|
||||
Burst: 5,
|
||||
QPS: 1,
|
||||
CacheSize: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "valid multiple",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "Server",
|
||||
Burst: 5,
|
||||
QPS: 1,
|
||||
},
|
||||
{
|
||||
Type: "Namespace",
|
||||
Burst: 10,
|
||||
QPS: 2,
|
||||
CacheSize: 100,
|
||||
},
|
||||
{
|
||||
Type: "SourceAndObject",
|
||||
Burst: 25,
|
||||
QPS: 10,
|
||||
CacheSize: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "missing limits",
|
||||
config: eventratelimitapi.Configuration{},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "missing type",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Burst: 25,
|
||||
QPS: 10,
|
||||
CacheSize: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "unknown-type",
|
||||
Burst: 25,
|
||||
QPS: 10,
|
||||
CacheSize: 1000,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "missing burst",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "Server",
|
||||
QPS: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "missing qps",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "Server",
|
||||
Burst: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "negative cache size",
|
||||
config: eventratelimitapi.Configuration{
|
||||
Limits: []eventratelimitapi.Limit{
|
||||
{
|
||||
Type: "Namespace",
|
||||
Burst: 10,
|
||||
QPS: 2,
|
||||
CacheSize: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
errs := ValidateConfiguration(&tc.config)
|
||||
if e, a := tc.expectedResult, len(errs) == 0; e != a {
|
||||
if e {
|
||||
t.Errorf("%v: expected success: %v", tc.name, errs)
|
||||
} else {
|
||||
t.Errorf("%v: expected failure", tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/zz_generated.deepcopy.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
|
||||
// This file was autogenerated by deepcopy-gen. Do not edit it manually!
|
||||
|
||||
package eventratelimit
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Configuration) DeepCopyInto(out *Configuration) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Limits != nil {
|
||||
in, out := &in.Limits, &out.Limits
|
||||
*out = make([]Limit, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Configuration.
|
||||
func (in *Configuration) DeepCopy() *Configuration {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Configuration)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Configuration) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Limit) DeepCopyInto(out *Limit) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Limit.
|
||||
func (in *Limit) DeepCopy() *Limit {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Limit)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
57
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache.go
generated
vendored
Normal file
57
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/golang-lru"
|
||||
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
// cache is an interface for caching the limits of a particular type
|
||||
type cache interface {
|
||||
// get the rate limiter associated with the specified key
|
||||
get(key interface{}) flowcontrol.RateLimiter
|
||||
}
|
||||
|
||||
// singleCache is a cache that only stores a single, constant item
|
||||
type singleCache struct {
|
||||
// the single rate limiter held by the cache
|
||||
rateLimiter flowcontrol.RateLimiter
|
||||
}
|
||||
|
||||
func (c *singleCache) get(key interface{}) flowcontrol.RateLimiter {
|
||||
return c.rateLimiter
|
||||
}
|
||||
|
||||
// lruCache is a least-recently-used cache
|
||||
type lruCache struct {
|
||||
// factory to use to create new rate limiters
|
||||
rateLimiterFactory func() flowcontrol.RateLimiter
|
||||
// the actual LRU cache
|
||||
cache *lru.Cache
|
||||
}
|
||||
|
||||
func (c *lruCache) get(key interface{}) flowcontrol.RateLimiter {
|
||||
value, found := c.cache.Get(key)
|
||||
if !found {
|
||||
rateLimter := c.rateLimiterFactory()
|
||||
c.cache.Add(key, rateLimter)
|
||||
return rateLimter
|
||||
}
|
||||
return value.(flowcontrol.RateLimiter)
|
||||
}
|
119
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache_test.go
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/cache_test.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/golang-lru"
|
||||
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
func TestSingleCache(t *testing.T) {
|
||||
rateLimiter := flowcontrol.NewTokenBucketRateLimiter(1., 1)
|
||||
cache := singleCache{
|
||||
rateLimiter: rateLimiter,
|
||||
}
|
||||
cases := []interface{}{nil, "key1", "key2"}
|
||||
for _, tc := range cases {
|
||||
actual := cache.get(tc)
|
||||
if e, a := rateLimiter, actual; e != a {
|
||||
t.Errorf("unexpected entry in cache for key %v: expected %v, got %v", tc, e, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLRUCache(t *testing.T) {
|
||||
rateLimiters := []flowcontrol.RateLimiter{
|
||||
flowcontrol.NewTokenBucketRateLimiter(1., 1),
|
||||
flowcontrol.NewTokenBucketRateLimiter(2., 2),
|
||||
flowcontrol.NewTokenBucketRateLimiter(3., 3),
|
||||
flowcontrol.NewTokenBucketRateLimiter(4., 4),
|
||||
}
|
||||
nextRateLimiter := 0
|
||||
rateLimiterFactory := func() flowcontrol.RateLimiter {
|
||||
rateLimiter := rateLimiters[nextRateLimiter]
|
||||
nextRateLimiter++
|
||||
return rateLimiter
|
||||
}
|
||||
underlyingCache, err := lru.New(2)
|
||||
if err != nil {
|
||||
t.Fatalf("Could not create LRU cache: %v", err)
|
||||
}
|
||||
cache := lruCache{
|
||||
rateLimiterFactory: rateLimiterFactory,
|
||||
cache: underlyingCache,
|
||||
}
|
||||
cases := []struct {
|
||||
name string
|
||||
key int
|
||||
expected flowcontrol.RateLimiter
|
||||
}{
|
||||
{
|
||||
name: "first added",
|
||||
key: 0,
|
||||
expected: rateLimiters[0],
|
||||
},
|
||||
{
|
||||
name: "first obtained",
|
||||
key: 0,
|
||||
expected: rateLimiters[0],
|
||||
},
|
||||
{
|
||||
name: "second added",
|
||||
key: 1,
|
||||
expected: rateLimiters[1],
|
||||
},
|
||||
{
|
||||
name: "second obtained",
|
||||
key: 1,
|
||||
expected: rateLimiters[1],
|
||||
},
|
||||
{
|
||||
name: "first obtained second time",
|
||||
key: 0,
|
||||
expected: rateLimiters[0],
|
||||
},
|
||||
{
|
||||
name: "third added",
|
||||
key: 2,
|
||||
expected: rateLimiters[2],
|
||||
},
|
||||
{
|
||||
name: "third obtained",
|
||||
key: 2,
|
||||
expected: rateLimiters[2],
|
||||
},
|
||||
{
|
||||
name: "first obtained third time",
|
||||
key: 0,
|
||||
expected: rateLimiters[0],
|
||||
},
|
||||
{
|
||||
name: "second re-added after eviction",
|
||||
key: 1,
|
||||
expected: rateLimiters[3],
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
actual := cache.get(tc.key)
|
||||
if e, a := tc.expected, actual; e != a {
|
||||
t.Errorf("%v: unexpected entry in cache for key %v: expected %v, got %v", tc.name, tc.key, e, a)
|
||||
}
|
||||
}
|
||||
}
|
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/clock.go
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/clock.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// realClock implements flowcontrol.Clock in terms of standard time functions.
|
||||
type realClock struct{}
|
||||
|
||||
// Now is identical to time.Now.
|
||||
func (realClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Sleep is identical to time.Sleep.
|
||||
func (realClock) Sleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/config.go
generated
vendored
Normal file
72
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/config.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/install"
|
||||
eventratelimitv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
groupFactoryRegistry = make(announced.APIGroupFactoryRegistry)
|
||||
registry = registered.NewOrDie(os.Getenv("KUBE_API_VERSIONS"))
|
||||
scheme = runtime.NewScheme()
|
||||
codecs = serializer.NewCodecFactory(scheme)
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(groupFactoryRegistry, registry, scheme)
|
||||
}
|
||||
|
||||
// LoadConfiguration loads the provided configuration.
|
||||
func LoadConfiguration(config io.Reader) (*eventratelimitapi.Configuration, error) {
|
||||
// if no config is provided, return a default configuration
|
||||
if config == nil {
|
||||
externalConfig := &eventratelimitv1alpha1.Configuration{}
|
||||
scheme.Default(externalConfig)
|
||||
internalConfig := &eventratelimitapi.Configuration{}
|
||||
if err := scheme.Convert(externalConfig, internalConfig, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return internalConfig, nil
|
||||
}
|
||||
// we have a config so parse it.
|
||||
data, err := ioutil.ReadAll(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decoder := codecs.UniversalDecoder()
|
||||
decodedObj, err := runtime.Decode(decoder, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceQuotaConfiguration, ok := decodedObj.(*eventratelimitapi.Configuration)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
|
||||
}
|
||||
return resourceQuotaConfiguration, nil
|
||||
}
|
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
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 eventratelimit contains an admission controller that enforces a rate limit on events
|
||||
package eventratelimit // import "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit"
|
145
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/limitenforcer.go
generated
vendored
Normal file
145
vendor/k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/limitenforcer.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
||||
/*
|
||||
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 eventratelimit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/golang-lru"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
|
||||
)
|
||||
|
||||
const (
|
||||
// cache size to use if the user did not specify a cache size
|
||||
defaultCacheSize = 4096
|
||||
)
|
||||
|
||||
// limitEnforcer enforces a single type of event rate limit, such as server, namespace, or source+object
|
||||
type limitEnforcer struct {
|
||||
// type of this limit
|
||||
limitType eventratelimitapi.LimitType
|
||||
// cache for holding the rate limiters
|
||||
cache cache
|
||||
// a keyFunc which is responsible for computing a single key based on input
|
||||
keyFunc func(admission.Attributes) string
|
||||
}
|
||||
|
||||
func newLimitEnforcer(config eventratelimitapi.Limit, clock flowcontrol.Clock) (*limitEnforcer, error) {
|
||||
rateLimiterFactory := func() flowcontrol.RateLimiter {
|
||||
return flowcontrol.NewTokenBucketRateLimiterWithClock(float32(config.QPS), int(config.Burst), clock)
|
||||
}
|
||||
|
||||
if config.Type == eventratelimitapi.ServerLimitType {
|
||||
return &limitEnforcer{
|
||||
limitType: config.Type,
|
||||
cache: &singleCache{
|
||||
rateLimiter: rateLimiterFactory(),
|
||||
},
|
||||
keyFunc: getServerKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
cacheSize := int(config.CacheSize)
|
||||
if cacheSize == 0 {
|
||||
cacheSize = defaultCacheSize
|
||||
}
|
||||
underlyingCache, err := lru.New(cacheSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create lru cache: %v", err)
|
||||
}
|
||||
cache := &lruCache{
|
||||
rateLimiterFactory: rateLimiterFactory,
|
||||
cache: underlyingCache,
|
||||
}
|
||||
|
||||
var keyFunc func(admission.Attributes) string
|
||||
switch t := config.Type; t {
|
||||
case eventratelimitapi.NamespaceLimitType:
|
||||
keyFunc = getNamespaceKey
|
||||
case eventratelimitapi.UserLimitType:
|
||||
keyFunc = getUserKey
|
||||
case eventratelimitapi.SourceAndObjectLimitType:
|
||||
keyFunc = getSourceAndObjectKey
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown event rate limit type: %v", t)
|
||||
}
|
||||
|
||||
return &limitEnforcer{
|
||||
limitType: config.Type,
|
||||
cache: cache,
|
||||
keyFunc: keyFunc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (enforcer *limitEnforcer) accept(attr admission.Attributes) error {
|
||||
key := enforcer.keyFunc(attr)
|
||||
rateLimiter := enforcer.cache.get(key)
|
||||
|
||||
// ensure we have available rate
|
||||
allow := rateLimiter.TryAccept()
|
||||
|
||||
if !allow {
|
||||
return apierrors.NewTooManyRequestsError(fmt.Sprintf("limit reached on type %v for key %v", enforcer.limitType, key))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getServerKey(attr admission.Attributes) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// getNamespaceKey returns a cache key that is based on the namespace of the event request
|
||||
func getNamespaceKey(attr admission.Attributes) string {
|
||||
return attr.GetNamespace()
|
||||
}
|
||||
|
||||
// getUserKey returns a cache key that is based on the user of the event request
|
||||
func getUserKey(attr admission.Attributes) string {
|
||||
userInfo := attr.GetUserInfo()
|
||||
if userInfo == nil {
|
||||
return ""
|
||||
}
|
||||
return userInfo.GetName()
|
||||
}
|
||||
|
||||
// getSourceAndObjectKey returns a cache key that is based on the source+object of the event
|
||||
func getSourceAndObjectKey(attr admission.Attributes) string {
|
||||
object := attr.GetObject()
|
||||
if object == nil {
|
||||
return ""
|
||||
}
|
||||
event, ok := object.(*api.Event)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return strings.Join([]string{
|
||||
event.Source.Component,
|
||||
event.Source.Host,
|
||||
event.InvolvedObject.Kind,
|
||||
event.InvolvedObject.Namespace,
|
||||
event.InvolvedObject.Name,
|
||||
string(event.InvolvedObject.UID),
|
||||
event.InvolvedObject.APIVersion,
|
||||
}, "")
|
||||
}
|
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/BUILD
generated
vendored
Normal file
51
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/BUILD
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors: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/apiserver/pkg/registry/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/exec",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
146
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission.go
generated
vendored
Normal file
146
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 exec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("DenyEscalatingExec", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewDenyEscalatingExec(), nil
|
||||
})
|
||||
|
||||
// This is for legacy support of the DenyExecOnPrivileged admission controller. Most
|
||||
// of the time DenyEscalatingExec should be preferred.
|
||||
plugins.Register("DenyExecOnPrivileged", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewDenyExecOnPrivileged(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// DenyExec is an implementation of admission.Interface which says no to a pod/exec on
|
||||
// a pod using host based configurations.
|
||||
type DenyExec struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
|
||||
// these flags control which items will be checked to deny exec/attach
|
||||
hostIPC bool
|
||||
hostPID bool
|
||||
privileged bool
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &DenyExec{}
|
||||
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&DenyExec{})
|
||||
|
||||
// NewDenyEscalatingExec creates a new admission controller that denies an exec operation on a pod
|
||||
// using host based configurations.
|
||||
func NewDenyEscalatingExec() *DenyExec {
|
||||
return &DenyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: true,
|
||||
hostPID: true,
|
||||
privileged: true,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDenyExecOnPrivileged creates a new admission controller that is only checking the privileged
|
||||
// option. This is for legacy support of the DenyExecOnPrivileged admission controller. Most
|
||||
// of the time NewDenyEscalatingExec should be preferred.
|
||||
func NewDenyExecOnPrivileged() *DenyExec {
|
||||
return &DenyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: false,
|
||||
hostPID: false,
|
||||
privileged: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (d *DenyExec) Validate(a admission.Attributes) (err error) {
|
||||
connectRequest, ok := a.GetObject().(*rest.ConnectRequest)
|
||||
if !ok {
|
||||
return errors.NewBadRequest("a connect request was received, but could not convert the request object.")
|
||||
}
|
||||
// Only handle exec or attach requests on pods
|
||||
if connectRequest.ResourcePath != "pods/exec" && connectRequest.ResourcePath != "pods/attach" {
|
||||
return nil
|
||||
}
|
||||
pod, err := d.client.Core().Pods(a.GetNamespace()).Get(connectRequest.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
if d.hostPID && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostPID {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host pid"))
|
||||
}
|
||||
|
||||
if d.hostIPC && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostIPC {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a container using host ipc"))
|
||||
}
|
||||
|
||||
if d.privileged && isPrivileged(pod) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot exec into or attach to a privileged container"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPrivileged will return true a pod has any privileged containers
|
||||
func isPrivileged(pod *api.Pod) bool {
|
||||
for _, c := range pod.Spec.InitContainers {
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil {
|
||||
continue
|
||||
}
|
||||
if *c.SecurityContext.Privileged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, c := range pod.Spec.Containers {
|
||||
if c.SecurityContext == nil || c.SecurityContext.Privileged == nil {
|
||||
continue
|
||||
}
|
||||
if *c.SecurityContext.Privileged {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
|
||||
func (d *DenyExec) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
d.client = client
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (d *DenyExec) ValidateInitialization() error {
|
||||
if d.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/exec/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
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 exec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
)
|
||||
|
||||
// newAllowEscalatingExec returns `admission.Interface` that allows execution on
|
||||
// "hostIPC", "hostPID" and "privileged".
|
||||
func newAllowEscalatingExec() *DenyExec {
|
||||
return &DenyExec{
|
||||
Handler: admission.NewHandler(admission.Connect),
|
||||
hostIPC: false,
|
||||
hostPID: false,
|
||||
privileged: false,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
privPod := validPod("privileged")
|
||||
priv := true
|
||||
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
|
||||
hostPIDPod := validPod("hostPID")
|
||||
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostPIDPod.Spec.SecurityContext.HostPID = true
|
||||
|
||||
hostIPCPod := validPod("hostIPC")
|
||||
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostIPCPod.Spec.SecurityContext.HostIPC = true
|
||||
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
shouldAccept bool
|
||||
}{
|
||||
"priv": {
|
||||
shouldAccept: false,
|
||||
pod: privPod,
|
||||
},
|
||||
"hostPID": {
|
||||
shouldAccept: false,
|
||||
pod: hostPIDPod,
|
||||
},
|
||||
"hostIPC": {
|
||||
shouldAccept: false,
|
||||
pod: hostIPCPod,
|
||||
},
|
||||
"non privileged": {
|
||||
shouldAccept: true,
|
||||
pod: validPod("nonPrivileged"),
|
||||
},
|
||||
}
|
||||
|
||||
// Get the direct object though to allow testAdmission to inject the client
|
||||
handler := NewDenyEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// run with a permissive config and all cases should pass
|
||||
handler = newAllowEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, true)
|
||||
}
|
||||
|
||||
// run against an init container
|
||||
handler = NewDenyEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc.pod.Spec.InitContainers = tc.pod.Spec.Containers
|
||||
tc.pod.Spec.Containers = nil
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// run with a permissive config and all cases should pass
|
||||
handler = newAllowEscalatingExec()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, true)
|
||||
}
|
||||
}
|
||||
|
||||
func testAdmission(t *testing.T, pod *api.Pod, handler *DenyExec, shouldAccept bool) {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("get", "pods", func(action core.Action) (bool, runtime.Object, error) {
|
||||
if action.(core.GetAction).GetName() == pod.Name {
|
||||
return true, pod, nil
|
||||
}
|
||||
t.Errorf("Unexpected API call: %#v", action)
|
||||
return true, nil, nil
|
||||
})
|
||||
|
||||
handler.SetInternalKubeClientSet(mockClient)
|
||||
admission.ValidateInitialization(handler)
|
||||
|
||||
// pods/exec
|
||||
{
|
||||
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/exec"}
|
||||
err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "exec", admission.Connect, nil))
|
||||
if shouldAccept && err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
if !shouldAccept && err == nil {
|
||||
t.Errorf("An error was expected from the admission handler. Received nil")
|
||||
}
|
||||
}
|
||||
|
||||
// pods/attach
|
||||
{
|
||||
req := &rest.ConnectRequest{Name: pod.Name, ResourcePath: "pods/attach"}
|
||||
err := handler.Validate(admission.NewAttributesRecord(req, nil, api.Kind("Pod").WithVersion("version"), "test", "name", api.Resource("pods").WithVersion("version"), "attach", admission.Connect, nil))
|
||||
if shouldAccept && err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler: %v", err)
|
||||
}
|
||||
if !shouldAccept && err == nil {
|
||||
t.Errorf("An error was expected from the admission handler. Received nil")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test to ensure legacy admission controller works as expected.
|
||||
func TestDenyExecOnPrivileged(t *testing.T) {
|
||||
privPod := validPod("privileged")
|
||||
priv := true
|
||||
privPod.Spec.Containers[0].SecurityContext = &api.SecurityContext{
|
||||
Privileged: &priv,
|
||||
}
|
||||
|
||||
hostPIDPod := validPod("hostPID")
|
||||
hostPIDPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostPIDPod.Spec.SecurityContext.HostPID = true
|
||||
|
||||
hostIPCPod := validPod("hostIPC")
|
||||
hostIPCPod.Spec.SecurityContext = &api.PodSecurityContext{}
|
||||
hostIPCPod.Spec.SecurityContext.HostIPC = true
|
||||
|
||||
testCases := map[string]struct {
|
||||
pod *api.Pod
|
||||
shouldAccept bool
|
||||
}{
|
||||
"priv": {
|
||||
shouldAccept: false,
|
||||
pod: privPod,
|
||||
},
|
||||
"hostPID": {
|
||||
shouldAccept: true,
|
||||
pod: hostPIDPod,
|
||||
},
|
||||
"hostIPC": {
|
||||
shouldAccept: true,
|
||||
pod: hostIPCPod,
|
||||
},
|
||||
"non privileged": {
|
||||
shouldAccept: true,
|
||||
pod: validPod("nonPrivileged"),
|
||||
},
|
||||
}
|
||||
|
||||
// Get the direct object though to allow testAdmission to inject the client
|
||||
handler := NewDenyExecOnPrivileged()
|
||||
|
||||
for _, tc := range testCases {
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
|
||||
// test init containers
|
||||
for _, tc := range testCases {
|
||||
tc.pod.Spec.InitContainers = tc.pod.Spec.Containers
|
||||
tc.pod.Spec.Containers = nil
|
||||
testAdmission(t, tc.pod, handler, tc.shouldAccept)
|
||||
}
|
||||
}
|
||||
|
||||
func validPod(name string) *api.Pod {
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{Name: "ctr1", Image: "image"},
|
||||
{Name: "ctr2", Image: "image2"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
42
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/BUILD
generated
vendored
Normal file
42
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/BUILD
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource: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"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
94
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission.go
generated
vendored
Normal file
94
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/*
|
||||
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 extendedresourcetoleration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
)
|
||||
|
||||
// Register is called by the apiserver to register the plugin factory.
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("ExtendedResourceToleration", func(config io.Reader) (admission.Interface, error) {
|
||||
return newExtendedResourceToleration(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// newExtendedResourceToleration creates a new instance of the ExtendedResourceToleration admission controller.
|
||||
func newExtendedResourceToleration() *plugin {
|
||||
return &plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we are implementing the interface.
|
||||
var _ admission.MutationInterface = &plugin{}
|
||||
|
||||
type plugin struct {
|
||||
*admission.Handler
|
||||
}
|
||||
|
||||
// Admit updates the toleration of a pod based on the resources requested by it.
|
||||
// If an extended resource of name "example.com/device" is requested, it adds
|
||||
// a toleration with key "example.com/device", operator "Exists" and effect "NoSchedule".
|
||||
// The rationale for this is described in:
|
||||
// https://github.com/kubernetes/kubernetes/issues/55080
|
||||
func (p *plugin) Admit(attributes admission.Attributes) error {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != core.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*core.Pod)
|
||||
if !ok {
|
||||
return errors.NewBadRequest(fmt.Sprintf("expected *core.Pod but got %T", attributes.GetObject()))
|
||||
}
|
||||
|
||||
resources := sets.String{}
|
||||
for _, container := range pod.Spec.Containers {
|
||||
for resourceName := range container.Resources.Requests {
|
||||
if helper.IsExtendedResourceName(resourceName) {
|
||||
resources.Insert(string(resourceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, container := range pod.Spec.InitContainers {
|
||||
for resourceName := range container.Resources.Requests {
|
||||
if helper.IsExtendedResourceName(resourceName) {
|
||||
resources.Insert(string(resourceName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Doing .List() so that we get a stable sorted list.
|
||||
// This allows us to test adding tolerations for multiple extended resources.
|
||||
for _, resource := range resources.List() {
|
||||
helper.AddOrUpdateTolerationInPod(pod, &core.Toleration{
|
||||
Key: resource,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
382
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission_test.go
generated
vendored
Normal file
382
vendor/k8s.io/kubernetes/plugin/pkg/admission/extendedresourcetoleration/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,382 @@
|
||||
/*
|
||||
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 extendedresourcetoleration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
)
|
||||
|
||||
func TestAdmit(t *testing.T) {
|
||||
|
||||
plugin := newExtendedResourceToleration()
|
||||
|
||||
containerRequestingCPU := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceCPU: *resource.NewQuantity(2, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
containerRequestingMemory := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceMemory: *resource.NewQuantity(2048, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
extendedResource1 := "example.com/device-ek"
|
||||
extendedResource2 := "example.com/device-do"
|
||||
|
||||
containerRequestingExtendedResource1 := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceName(extendedResource1): *resource.NewQuantity(1, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
containerRequestingExtendedResource2 := core.Container{
|
||||
Resources: core.ResourceRequirements{
|
||||
Requests: core.ResourceList{
|
||||
core.ResourceName(extendedResource2): *resource.NewQuantity(2, resource.DecimalSI),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
requestedPod core.Pod
|
||||
expectedPod core.Pod
|
||||
}{
|
||||
{
|
||||
description: "empty pod without any extended resources, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container without any extended resources, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with init container without any extended resources, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container with extended resource, expect toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with init container with extended resource, expect toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource2,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with existing tolerations and container with extended resource, expect existing tolerations to be preserved and new toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: "foo",
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "bar",
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with multiple extended resources, expect multiple tolerations to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
InitContainers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingExtendedResource2,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
// Note the order, it's sorted by the Key
|
||||
{
|
||||
Key: extendedResource2,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container requesting extended resource and existing correct toleration, expect no change in tolerations",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with container requesting extended resource and existing toleration with the same key but different effect and value, expect existing tolerations to be preserved and new toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "foo",
|
||||
Effect: core.TaintEffectNoExecute,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpEqual,
|
||||
Value: "foo",
|
||||
Effect: core.TaintEffectNoExecute,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "pod with wildcard toleration and container requesting extended resource, expect existing tolerations to be preserved and new toleration to be added",
|
||||
requestedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedPod: core.Pod{
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
containerRequestingCPU,
|
||||
containerRequestingMemory,
|
||||
containerRequestingExtendedResource1,
|
||||
},
|
||||
Tolerations: []core.Toleration{
|
||||
{
|
||||
Operator: core.TolerationOpExists,
|
||||
},
|
||||
{
|
||||
Key: extendedResource1,
|
||||
Operator: core.TolerationOpExists,
|
||||
Effect: core.TaintEffectNoSchedule,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
err := plugin.Admit(admission.NewAttributesRecord(&test.requestedPod, nil, core.Kind("Pod").WithVersion("version"), "foo", "name", core.Resource("pods").WithVersion("version"), "", "ignored", nil))
|
||||
if err != nil {
|
||||
t.Errorf("[%d: %s] unexpected error %v for pod %+v", i, test.description, err, test.requestedPod)
|
||||
}
|
||||
|
||||
if !helper.Semantic.DeepEqual(test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations) {
|
||||
t.Errorf("[%d: %s] expected %#v got %#v", i, test.description, test.expectedPod.Spec.Tolerations, test.requestedPod.Spec.Tolerations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandles(t *testing.T) {
|
||||
plugin := newExtendedResourceToleration()
|
||||
tests := map[admission.Operation]bool{
|
||||
admission.Create: true,
|
||||
admission.Update: true,
|
||||
admission.Delete: false,
|
||||
admission.Connect: false,
|
||||
}
|
||||
for op, expected := range tests {
|
||||
result := plugin.Handles(op)
|
||||
if result != expected {
|
||||
t.Errorf("Unexpected result for operation %s: %v\n", op, result)
|
||||
}
|
||||
}
|
||||
}
|
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/BUILD
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["gc_admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/gc",
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["gc_admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/gc",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
273
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go
generated
vendored
Normal file
273
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go
generated
vendored
Normal file
@ -0,0 +1,273 @@
|
||||
/*
|
||||
Copyright 2016 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 gc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("OwnerReferencesPermissionEnforcement", func(config io.Reader) (admission.Interface, error) {
|
||||
// the pods/status endpoint is ignored by this plugin since old kubelets
|
||||
// corrupt them. the pod status strategy ensures status updates cannot mutate
|
||||
// ownerRef.
|
||||
whiteList := []whiteListItem{
|
||||
{
|
||||
groupResource: schema.GroupResource{Resource: "pods"},
|
||||
subresource: "status",
|
||||
},
|
||||
}
|
||||
return &gcPermissionsEnforcement{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
whiteList: whiteList,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
// gcPermissionsEnforcement is an implementation of admission.Interface.
|
||||
type gcPermissionsEnforcement struct {
|
||||
*admission.Handler
|
||||
|
||||
authorizer authorizer.Authorizer
|
||||
|
||||
restMapper meta.RESTMapper
|
||||
|
||||
// items in this whitelist are ignored upon admission.
|
||||
// any item in this list must protect against ownerRef mutations
|
||||
// via strategy enforcement.
|
||||
whiteList []whiteListItem
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &gcPermissionsEnforcement{}
|
||||
|
||||
// whiteListItem describes an entry in a whitelist ignored by gc permission enforcement.
|
||||
type whiteListItem struct {
|
||||
groupResource schema.GroupResource
|
||||
subresource string
|
||||
}
|
||||
|
||||
// isWhiteListed returns true if the specified item is in the whitelist.
|
||||
func (a *gcPermissionsEnforcement) isWhiteListed(groupResource schema.GroupResource, subresource string) bool {
|
||||
for _, item := range a.whiteList {
|
||||
if item.groupResource == groupResource && item.subresource == subresource {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) Validate(attributes admission.Attributes) (err error) {
|
||||
// // if the request is in the whitelist, we skip mutation checks for this resource.
|
||||
if a.isWhiteListed(attributes.GetResource().GroupResource(), attributes.GetSubresource()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we aren't changing owner references, then the edit is always allowed
|
||||
if !isChangingOwnerReference(attributes.GetObject(), attributes.GetOldObject()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
deleteAttributes := authorizer.AttributesRecord{
|
||||
User: attributes.GetUserInfo(),
|
||||
Verb: "delete",
|
||||
Namespace: attributes.GetNamespace(),
|
||||
APIGroup: attributes.GetResource().Group,
|
||||
APIVersion: attributes.GetResource().Version,
|
||||
Resource: attributes.GetResource().Resource,
|
||||
Subresource: attributes.GetSubresource(),
|
||||
Name: attributes.GetName(),
|
||||
ResourceRequest: true,
|
||||
Path: "",
|
||||
}
|
||||
decision, reason, err := a.authorizer.Authorize(deleteAttributes)
|
||||
if decision != authorizer.DecisionAllow {
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err))
|
||||
}
|
||||
|
||||
// Further check if the user is setting ownerReference.blockOwnerDeletion to
|
||||
// true. If so, only allows the change if the user has delete permission of
|
||||
// the _OWNER_
|
||||
newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject())
|
||||
for _, ref := range newBlockingRefs {
|
||||
records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion in this case because cannot find RESTMapping for APIVersion %s Kind %s: %v, %v", ref.APIVersion, ref.Kind, reason, err))
|
||||
}
|
||||
// Multiple records are returned if ref.Kind could map to multiple
|
||||
// resources. User needs to have delete permission on all the
|
||||
// matched Resources.
|
||||
for _, record := range records {
|
||||
decision, reason, err := a.authorizer.Authorize(record)
|
||||
if decision != authorizer.DecisionAllow {
|
||||
return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
|
||||
newMeta, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return false
|
||||
}
|
||||
|
||||
if oldObj == nil {
|
||||
return len(newMeta.GetOwnerReferences()) > 0
|
||||
}
|
||||
oldMeta, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return false
|
||||
}
|
||||
|
||||
// compare the old and new. If they aren't the same, then we're trying to change an ownerRef
|
||||
oldOwners := oldMeta.GetOwnerReferences()
|
||||
newOwners := newMeta.GetOwnerReferences()
|
||||
if len(oldOwners) != len(newOwners) {
|
||||
return true
|
||||
}
|
||||
for i := range oldOwners {
|
||||
if !apiequality.Semantic.DeepEqual(oldOwners[i], newOwners[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Translates ref to a DeleteAttribute deleting the object referred by the ref.
|
||||
// OwnerReference only records the object kind, which might map to multiple
|
||||
// resources, so multiple DeleteAttribute might be returned.
|
||||
func (a *gcPermissionsEnforcement) ownerRefToDeleteAttributeRecords(ref metav1.OwnerReference, attributes admission.Attributes) ([]authorizer.AttributesRecord, error) {
|
||||
var ret []authorizer.AttributesRecord
|
||||
groupVersion, err := schema.ParseGroupVersion(ref.APIVersion)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
mappings, err := a.restMapper.RESTMappings(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
for _, mapping := range mappings {
|
||||
ret = append(ret, authorizer.AttributesRecord{
|
||||
User: attributes.GetUserInfo(),
|
||||
Verb: "update",
|
||||
// ownerReference can only refer to an object in the same namespace, so attributes.GetNamespace() equals to the owner's namespace
|
||||
Namespace: attributes.GetNamespace(),
|
||||
APIGroup: groupVersion.Group,
|
||||
APIVersion: groupVersion.Version,
|
||||
Resource: mapping.Resource,
|
||||
Subresource: "finalizers",
|
||||
Name: ref.Name,
|
||||
ResourceRequest: true,
|
||||
Path: "",
|
||||
})
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// only keeps the blocking refs
|
||||
func blockingOwnerRefs(refs []metav1.OwnerReference) []metav1.OwnerReference {
|
||||
var ret []metav1.OwnerReference
|
||||
for _, ref := range refs {
|
||||
if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion == true {
|
||||
ret = append(ret, ref)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func indexByUID(refs []metav1.OwnerReference) map[types.UID]metav1.OwnerReference {
|
||||
ret := make(map[types.UID]metav1.OwnerReference)
|
||||
for _, ref := range refs {
|
||||
ret[ref.UID] = ref
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Returns new blocking ownerReferences, and references whose blockOwnerDeletion
|
||||
// field is changed from nil or false to true.
|
||||
func newBlockingOwnerDeletionRefs(newObj, oldObj runtime.Object) []metav1.OwnerReference {
|
||||
newMeta, err := meta.Accessor(newObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, we don't have the object reference
|
||||
return nil
|
||||
}
|
||||
newRefs := newMeta.GetOwnerReferences()
|
||||
blockingNewRefs := blockingOwnerRefs(newRefs)
|
||||
if len(blockingNewRefs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if oldObj == nil {
|
||||
return blockingNewRefs
|
||||
}
|
||||
oldMeta, err := meta.Accessor(oldObj)
|
||||
if err != nil {
|
||||
// if we don't have objectmeta, treat it as if all the ownerReference are newly created
|
||||
return blockingNewRefs
|
||||
}
|
||||
|
||||
var ret []metav1.OwnerReference
|
||||
indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
|
||||
for _, ref := range blockingNewRefs {
|
||||
oldRef, ok := indexedOldRefs[ref.UID]
|
||||
if !ok {
|
||||
// if ref is newly added, and it's blocking, then returns it.
|
||||
ret = append(ret, ref)
|
||||
continue
|
||||
}
|
||||
wasNotBlocking := oldRef.BlockOwnerDeletion == nil || *oldRef.BlockOwnerDeletion == false
|
||||
if wasNotBlocking {
|
||||
ret = append(ret, ref)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
a.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) {
|
||||
a.restMapper = restMapper
|
||||
}
|
||||
|
||||
func (a *gcPermissionsEnforcement) ValidateInitialization() error {
|
||||
if a.authorizer == nil {
|
||||
return fmt.Errorf("missing authorizer")
|
||||
}
|
||||
if a.restMapper == nil {
|
||||
return fmt.Errorf("missing restMapper")
|
||||
}
|
||||
return nil
|
||||
}
|
522
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission_test.go
generated
vendored
Normal file
522
vendor/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission_test.go
generated
vendored
Normal file
@ -0,0 +1,522 @@
|
||||
/*
|
||||
Copyright 2016 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 gc
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
type fakeAuthorizer struct{}
|
||||
|
||||
func (fakeAuthorizer) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
username := a.GetUser().GetName()
|
||||
|
||||
if username == "non-deleter" {
|
||||
if a.GetVerb() == "delete" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
if username == "non-pod-deleter" {
|
||||
if a.GetVerb() == "delete" && a.GetResource() == "pods" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
if username == "non-rc-deleter" {
|
||||
if a.GetVerb() == "delete" && a.GetResource() == "replicationcontrollers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
// newGCPermissionsEnforcement returns the admission controller configured for testing.
|
||||
func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
|
||||
// the pods/status endpoint is ignored by this plugin since old kubelets
|
||||
// corrupt them. the pod status strategy ensures status updates cannot mutate
|
||||
// ownerRef.
|
||||
whiteList := []whiteListItem{
|
||||
{
|
||||
groupResource: schema.GroupResource{Resource: "pods"},
|
||||
subresource: "status",
|
||||
},
|
||||
}
|
||||
gcAdmit := &gcPermissionsEnforcement{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
whiteList: whiteList,
|
||||
}
|
||||
|
||||
genericPluginInitializer := initializer.New(nil, nil, fakeAuthorizer{}, nil)
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(nil, nil, nil, legacyscheme.Registry.RESTMapper(), nil)
|
||||
initializersChain := admission.PluginInitializers{}
|
||||
initializersChain = append(initializersChain, genericPluginInitializer)
|
||||
initializersChain = append(initializersChain, pluginInitializer)
|
||||
|
||||
initializersChain.Initialize(gcAdmit)
|
||||
return gcAdmit, nil
|
||||
}
|
||||
|
||||
func TestGCAdmission(t *testing.T) {
|
||||
expectNoError := func(err error) bool {
|
||||
return err == nil
|
||||
}
|
||||
expectCantSetOwnerRefError := func(err error) bool {
|
||||
return strings.Contains(err.Error(), "cannot set an ownerRef on a resource you can't delete")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
oldObj runtime.Object
|
||||
newObj runtime.Object
|
||||
|
||||
checkError func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "super-user, create, no objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, create, no objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, create, objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, no objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, create, objectref change, but not a pod",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("not-pods"),
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
|
||||
{
|
||||
name: "super-user, update, no objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, no objectref change two",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, objectref change",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, no objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, no objectref change two",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, objectref change",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-deleter, update, objectref change two",
|
||||
username: "non-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}, {Name: "second"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, no objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update status, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
subresource: "status",
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, objectref change",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectCantSetOwnerRefError,
|
||||
},
|
||||
{
|
||||
name: "non-pod-deleter, update, objectref change, but not a pod",
|
||||
username: "non-pod-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("not-pods"),
|
||||
oldObj: &api.Pod{},
|
||||
newObj: &api.Pod{ObjectMeta: metav1.ObjectMeta{OwnerReferences: []metav1.OwnerReference{{Name: "first"}}}},
|
||||
checkError: expectNoError,
|
||||
},
|
||||
}
|
||||
gcAdmit, err := newGCPermissionsEnforcement()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
operation := admission.Create
|
||||
if tc.oldObj != nil {
|
||||
operation = admission.Update
|
||||
}
|
||||
user := &user.DefaultInfo{Name: tc.username}
|
||||
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user)
|
||||
|
||||
err := gcAdmit.Validate(attributes)
|
||||
if !tc.checkError(err) {
|
||||
t.Errorf("%v: unexpected err: %v", tc.name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockOwnerDeletionAdmission(t *testing.T) {
|
||||
podWithOwnerRefs := func(refs ...metav1.OwnerReference) *api.Pod {
|
||||
var refSlice []metav1.OwnerReference
|
||||
for _, ref := range refs {
|
||||
refSlice = append(refSlice, ref)
|
||||
}
|
||||
return &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
OwnerReferences: refSlice,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
getTrueVar := func() *bool {
|
||||
ret := true
|
||||
return &ret
|
||||
}
|
||||
|
||||
getFalseVar := func() *bool {
|
||||
ret := false
|
||||
return &ret
|
||||
}
|
||||
blockRC1 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
BlockOwnerDeletion: getTrueVar(),
|
||||
}
|
||||
blockRC2 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
BlockOwnerDeletion: getTrueVar(),
|
||||
}
|
||||
notBlockRC1 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
BlockOwnerDeletion: getFalseVar(),
|
||||
}
|
||||
notBlockRC2 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
BlockOwnerDeletion: getFalseVar(),
|
||||
}
|
||||
nilBlockRC1 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc1",
|
||||
}
|
||||
nilBlockRC2 := metav1.OwnerReference{
|
||||
APIVersion: "v1",
|
||||
Kind: "ReplicationController",
|
||||
Name: "rc2",
|
||||
}
|
||||
blockDS1 := metav1.OwnerReference{
|
||||
APIVersion: "extensions/v1beta1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds1",
|
||||
BlockOwnerDeletion: getTrueVar(),
|
||||
}
|
||||
notBlockDS1 := metav1.OwnerReference{
|
||||
APIVersion: "extensions/v1beta1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "ds1",
|
||||
BlockOwnerDeletion: getFalseVar(),
|
||||
}
|
||||
|
||||
expectNoError := func(err error) bool {
|
||||
return err == nil
|
||||
}
|
||||
expectCantSetBlockOwnerDeletionError := func(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), "cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on")
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
oldObj runtime.Object
|
||||
newObj runtime.Object
|
||||
|
||||
checkError func(error) bool
|
||||
}{
|
||||
// cases for create
|
||||
{
|
||||
name: "super-user, create, no ownerReferences",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, all ownerReferences have blockOwnerDeletion=false",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(notBlockRC1, notBlockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, create, some ownerReferences have blockOwnerDeletion=true",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(blockRC1, blockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, no ownerReferences",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, all ownerReferences have blockOwnerDeletion=false or nil",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(notBlockRC1, nilBlockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, some ownerReferences have blockOwnerDeletion=true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(blockRC1, notBlockRC2),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, create, some ownerReferences have blockOwnerDeletion=true, but are pointing to daemonset",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
newObj: podWithOwnerRefs(blockDS1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
// cases are for update
|
||||
{
|
||||
name: "super-user, update, no ownerReferences change blockOwnerDeletion",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(nilBlockRC1),
|
||||
newObj: podWithOwnerRefs(notBlockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, some ownerReferences change to blockOwnerDeletion=true",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(notBlockRC1),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "super-user, update, add new ownerReferences with blockOwnerDeletion=true",
|
||||
username: "super",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, no ownerReferences change blockOwnerDeletion",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(nilBlockRC1),
|
||||
newObj: podWithOwnerRefs(notBlockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change from blockOwnerDeletion=false to true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(notBlockRC1),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change from blockOwnerDeletion=nil to true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(nilBlockRC1),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change from blockOwnerDeletion=true to false",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(blockRC1),
|
||||
newObj: podWithOwnerRefs(notBlockRC1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, some ownerReferences change blockOwnerDeletion, but all such references are to daemonset",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(notBlockDS1),
|
||||
newObj: podWithOwnerRefs(blockDS1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, add new ownerReferences with blockOwnerDeletion=nil or false",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(notBlockRC1, nilBlockRC2),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, add new ownerReferences with blockOwnerDeletion=true",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(blockRC1),
|
||||
checkError: expectCantSetBlockOwnerDeletionError,
|
||||
},
|
||||
{
|
||||
name: "non-rc-deleter, update, add new ownerReferences with blockOwnerDeletion=true, but the references are to daemonset",
|
||||
username: "non-rc-deleter",
|
||||
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||
oldObj: podWithOwnerRefs(),
|
||||
newObj: podWithOwnerRefs(blockDS1),
|
||||
checkError: expectNoError,
|
||||
},
|
||||
}
|
||||
gcAdmit, err := newGCPermissionsEnforcement()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
operation := admission.Create
|
||||
if tc.oldObj != nil {
|
||||
operation = admission.Update
|
||||
}
|
||||
user := &user.DefaultInfo{Name: tc.username}
|
||||
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, user)
|
||||
|
||||
err := gcAdmit.Validate(attributes)
|
||||
if !tc.checkError(err) {
|
||||
t.Errorf("%v: unexpected err: %v", tc.name, err)
|
||||
}
|
||||
}
|
||||
}
|
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/BUILD
generated
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
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",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/cache:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/webhook:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"admission_test.go",
|
||||
"certs_test.go",
|
||||
"config_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/imagepolicy/install:go_default_library",
|
||||
"//vendor/k8s.io/api/imagepolicy/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
251
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
251
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission.go
generated
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
/*
|
||||
Copyright 2016 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 imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/imagepolicy/v1alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
// install the clientgo image policy API for use with api registry
|
||||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
)
|
||||
|
||||
var (
|
||||
groupVersions = []schema.GroupVersion{v1alpha1.SchemeGroupVersion}
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("ImagePolicyWebhook", func(config io.Reader) (admission.Interface, error) {
|
||||
newImagePolicyWebhook, err := NewImagePolicyWebhook(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newImagePolicyWebhook, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
webhook *webhook.GenericWebhook
|
||||
responseCache *cache.LRUExpireCache
|
||||
allowTTL time.Duration
|
||||
denyTTL time.Duration
|
||||
retryBackoff time.Duration
|
||||
defaultAllow bool
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Plugin{}
|
||||
|
||||
func (a *Plugin) statusTTL(status v1alpha1.ImageReviewStatus) time.Duration {
|
||||
if status.Allowed {
|
||||
return a.allowTTL
|
||||
}
|
||||
return a.denyTTL
|
||||
}
|
||||
|
||||
// Filter out annotations that don't match *.image-policy.k8s.io/*
|
||||
func (a *Plugin) filterAnnotations(allAnnotations map[string]string) map[string]string {
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range allAnnotations {
|
||||
if strings.Contains(k, ".image-policy.k8s.io/") {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// Function to call on webhook failure; behavior determined by defaultAllow flag
|
||||
func (a *Plugin) webhookError(pod *api.Pod, attributes admission.Attributes, err error) error {
|
||||
if err != nil {
|
||||
glog.V(2).Infof("error contacting webhook backend: %s", err)
|
||||
if a.defaultAllow {
|
||||
annotations := pod.GetAnnotations()
|
||||
if annotations == nil {
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
annotations[api.ImagePolicyFailedOpenKey] = "true"
|
||||
pod.ObjectMeta.SetAnnotations(annotations)
|
||||
glog.V(2).Infof("resource allowed in spite of webhook backend failure")
|
||||
return nil
|
||||
}
|
||||
glog.V(2).Infof("resource not allowed due to webhook backend failure ")
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (a *Plugin) Validate(attributes admission.Attributes) (err error) {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
if attributes.GetSubresource() != "" || attributes.GetResource().GroupResource() != api.Resource("pods") {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := attributes.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
|
||||
// Build list of ImageReviewContainerSpec
|
||||
var imageReviewContainerSpecs []v1alpha1.ImageReviewContainerSpec
|
||||
containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers))
|
||||
containers = append(containers, pod.Spec.Containers...)
|
||||
containers = append(containers, pod.Spec.InitContainers...)
|
||||
for _, c := range containers {
|
||||
imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{
|
||||
Image: c.Image,
|
||||
})
|
||||
}
|
||||
imageReview := v1alpha1.ImageReview{
|
||||
Spec: v1alpha1.ImageReviewSpec{
|
||||
Containers: imageReviewContainerSpecs,
|
||||
Annotations: a.filterAnnotations(pod.Annotations),
|
||||
Namespace: attributes.GetNamespace(),
|
||||
},
|
||||
}
|
||||
if err := a.admitPod(pod, attributes, &imageReview); err != nil {
|
||||
return admission.NewForbidden(attributes, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Plugin) admitPod(pod *api.Pod, attributes admission.Attributes, review *v1alpha1.ImageReview) error {
|
||||
cacheKey, err := json.Marshal(review.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if entry, ok := a.responseCache.Get(string(cacheKey)); ok {
|
||||
review.Status = entry.(v1alpha1.ImageReviewStatus)
|
||||
} else {
|
||||
result := a.webhook.WithExponentialBackoff(func() rest.Result {
|
||||
return a.webhook.RestClient.Post().Body(review).Do()
|
||||
})
|
||||
|
||||
if err := result.Error(); err != nil {
|
||||
return a.webhookError(pod, attributes, err)
|
||||
}
|
||||
var statusCode int
|
||||
if result.StatusCode(&statusCode); statusCode < 200 || statusCode >= 300 {
|
||||
return a.webhookError(pod, attributes, fmt.Errorf("Error contacting webhook: %d", statusCode))
|
||||
}
|
||||
|
||||
if err := result.Into(review); err != nil {
|
||||
return a.webhookError(pod, attributes, err)
|
||||
}
|
||||
|
||||
a.responseCache.Add(string(cacheKey), review.Status, a.statusTTL(review.Status))
|
||||
}
|
||||
|
||||
if !review.Status.Allowed {
|
||||
if len(review.Status.Reason) > 0 {
|
||||
return fmt.Errorf("image policy webhook backend denied one or more images: %s", review.Status.Reason)
|
||||
}
|
||||
return errors.New("one or more images rejected by webhook backend")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewImagePolicyWebhook a new ImagePolicyWebhook plugin from the provided config file.
|
||||
// The config file is specified by --admission-control-config-file and has the
|
||||
// following format for a webhook:
|
||||
//
|
||||
// {
|
||||
// "imagePolicy": {
|
||||
// "kubeConfigFile": "path/to/kubeconfig/for/backend",
|
||||
// "allowTTL": 30, # time in s to cache approval
|
||||
// "denyTTL": 30, # time in s to cache denial
|
||||
// "retryBackoff": 500, # time in ms to wait between retries
|
||||
// "defaultAllow": true # determines behavior if the webhook backend fails
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// The config file may be json or yaml.
|
||||
//
|
||||
// The kubeconfig property refers to another file in the kubeconfig format which
|
||||
// specifies how to connect to the webhook backend.
|
||||
//
|
||||
// The kubeconfig's cluster field is used to refer to the remote service, user refers to the returned authorizer.
|
||||
//
|
||||
// # clusters refers to the remote service.
|
||||
// clusters:
|
||||
// - name: name-of-remote-imagepolicy-service
|
||||
// cluster:
|
||||
// certificate-authority: /path/to/ca.pem # CA for verifying the remote service.
|
||||
// server: https://images.example.com/policy # URL of remote service to query. Must use 'https'.
|
||||
//
|
||||
// # users refers to the API server's webhook configuration.
|
||||
// users:
|
||||
// - name: name-of-api-server
|
||||
// user:
|
||||
// client-certificate: /path/to/cert.pem # cert for the webhook plugin to use
|
||||
// client-key: /path/to/key.pem # key matching the cert
|
||||
//
|
||||
// For additional HTTP configuration, refer to the kubeconfig documentation
|
||||
// http://kubernetes.io/v1.1/docs/user-guide/kubeconfig-file.html.
|
||||
func NewImagePolicyWebhook(configFile io.Reader) (*Plugin, error) {
|
||||
if configFile == nil {
|
||||
return nil, fmt.Errorf("no config specified")
|
||||
}
|
||||
|
||||
// TODO: move this to a versioned configuration file format
|
||||
var config AdmissionConfig
|
||||
d := yaml.NewYAMLOrJSONDecoder(configFile, 4096)
|
||||
err := d.Decode(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
whConfig := config.ImagePolicyWebhook
|
||||
if err := normalizeWebhookConfig(&whConfig); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gw, err := webhook.NewGenericWebhook(legacyscheme.Registry, legacyscheme.Codecs, whConfig.KubeConfigFile, groupVersions, whConfig.RetryBackoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
webhook: gw,
|
||||
responseCache: cache.NewLRUExpireCache(1024),
|
||||
allowTTL: whConfig.AllowTTL,
|
||||
denyTTL: whConfig.DenyTTL,
|
||||
defaultAllow: whConfig.DefaultAllow,
|
||||
}, nil
|
||||
}
|
948
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
948
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,948 @@
|
||||
/*
|
||||
Copyright 2016 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 imagepolicy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/imagepolicy/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/tools/clientcmd/api/v1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
)
|
||||
|
||||
const defaultConfigTmplJSON = `
|
||||
{
|
||||
"imagePolicy": {
|
||||
"kubeConfigFile": "{{ .KubeConfig }}",
|
||||
"allowTTL": {{ .AllowTTL }},
|
||||
"denyTTL": {{ .DenyTTL }},
|
||||
"retryBackoff": {{ .RetryBackoff }},
|
||||
"defaultAllow": {{ .DefaultAllow }}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const defaultConfigTmplYAML = `
|
||||
imagePolicy:
|
||||
kubeConfigFile: "{{ .KubeConfig }}"
|
||||
allowTTL: {{ .AllowTTL }}
|
||||
denyTTL: {{ .DenyTTL }}
|
||||
retryBackoff: {{ .RetryBackoff }}
|
||||
defaultAllow: {{ .DefaultAllow }}
|
||||
`
|
||||
|
||||
func TestNewFromConfig(t *testing.T) {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
data := struct {
|
||||
CA string
|
||||
Cert string
|
||||
Key string
|
||||
}{
|
||||
CA: filepath.Join(dir, "ca.pem"),
|
||||
Cert: filepath.Join(dir, "clientcert.pem"),
|
||||
Key: filepath.Join(dir, "clientkey.pem"),
|
||||
}
|
||||
|
||||
files := []struct {
|
||||
name string
|
||||
data []byte
|
||||
}{
|
||||
{data.CA, caCert},
|
||||
{data.Cert, clientCert},
|
||||
{data.Key, clientKey},
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := ioutil.WriteFile(file.name, file.data, 0400); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
kubeConfigTmpl string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
msg: "a single cluster and single user",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
users:
|
||||
- name: a cluster
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with no context",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
msg: "multiple clusters with a context",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: barfoo
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
msg: "cluster with bad certificate path specified",
|
||||
kubeConfigTmpl: `
|
||||
clusters:
|
||||
- cluster:
|
||||
certificate-authority: a bad certificate path
|
||||
server: https://admission.example.com
|
||||
name: foobar
|
||||
- cluster:
|
||||
certificate-authority: {{ .CA }}
|
||||
server: https://admission.example.com
|
||||
name: barfoo
|
||||
users:
|
||||
- name: a name
|
||||
user:
|
||||
client-certificate: {{ .Cert }}
|
||||
client-key: {{ .Key }}
|
||||
contexts:
|
||||
- name: default
|
||||
context:
|
||||
cluster: foobar
|
||||
user: a name
|
||||
current-context: default
|
||||
`,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
err := func() error {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
|
||||
tmpl, err := template.New("test").Parse(tt.kubeConfigTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
if err := tmpl.Execute(tempfile, data); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
tempconfigfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pc := tempconfigfile.Name()
|
||||
defer os.Remove(pc)
|
||||
|
||||
configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplJSON)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
dataConfig := struct {
|
||||
KubeConfig string
|
||||
AllowTTL int
|
||||
DenyTTL int
|
||||
RetryBackoff int
|
||||
DefaultAllow bool
|
||||
}{
|
||||
KubeConfig: p,
|
||||
AllowTTL: 500,
|
||||
DenyTTL: 500,
|
||||
RetryBackoff: 500,
|
||||
DefaultAllow: true,
|
||||
}
|
||||
if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
|
||||
return fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new admission controller
|
||||
configFile, err := os.Open(pc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read test config: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
|
||||
_, err = NewImagePolicyWebhook(configFile)
|
||||
return err
|
||||
}()
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("failed to load plugin from config %q: %v", tt.msg, err)
|
||||
}
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("wanted an error when loading config, did not get one: %q", tt.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service mocks a remote service.
|
||||
type Service interface {
|
||||
Review(*v1alpha1.ImageReview)
|
||||
HTTPStatusCode() int
|
||||
}
|
||||
|
||||
// NewTestServer wraps a Service as an httptest.Server.
|
||||
func NewTestServer(s Service, cert, key, caCert []byte) (*httptest.Server, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if cert != nil {
|
||||
cert, err := tls.X509KeyPair(cert, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
}
|
||||
|
||||
if caCert != nil {
|
||||
rootCAs := x509.NewCertPool()
|
||||
rootCAs.AppendCertsFromPEM(caCert)
|
||||
if tlsConfig == nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
}
|
||||
tlsConfig.ClientCAs = rootCAs
|
||||
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
serveHTTP := func(w http.ResponseWriter, r *http.Request) {
|
||||
var review v1alpha1.ImageReview
|
||||
if err := json.NewDecoder(r.Body).Decode(&review); err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if s.HTTPStatusCode() < 200 || s.HTTPStatusCode() >= 300 {
|
||||
http.Error(w, "HTTP Error", s.HTTPStatusCode())
|
||||
return
|
||||
}
|
||||
s.Review(&review)
|
||||
type status struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
resp := struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Kind string `json:"kind"`
|
||||
Status status `json:"status"`
|
||||
}{
|
||||
APIVersion: v1alpha1.SchemeGroupVersion.String(),
|
||||
Kind: "ImageReview",
|
||||
Status: status{review.Status.Allowed, review.Status.Reason},
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(serveHTTP))
|
||||
server.TLS = tlsConfig
|
||||
server.StartTLS()
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// A service that can be set to allow all or deny all authorization requests.
|
||||
type mockService struct {
|
||||
allow bool
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (m *mockService) Review(r *v1alpha1.ImageReview) {
|
||||
r.Status.Allowed = m.allow
|
||||
|
||||
// hardcoded overrides
|
||||
if r.Spec.Containers[0].Image == "good" {
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
|
||||
for _, c := range r.Spec.Containers {
|
||||
if c.Image == "bad" {
|
||||
r.Status.Allowed = false
|
||||
}
|
||||
}
|
||||
|
||||
if !r.Status.Allowed {
|
||||
r.Status.Reason = "not allowed"
|
||||
}
|
||||
}
|
||||
func (m *mockService) Allow() { m.allow = true }
|
||||
func (m *mockService) Deny() { m.allow = false }
|
||||
func (m *mockService) HTTPStatusCode() int { return m.statusCode }
|
||||
|
||||
// newImagePolicyWebhook creates a temporary kubeconfig file from the provided arguments and attempts to load
|
||||
// a new newImagePolicyWebhook from it.
|
||||
func newImagePolicyWebhook(callbackURL string, clientCert, clientKey, ca []byte, cacheTime time.Duration, defaultAllow bool) (*Plugin, error) {
|
||||
tempfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p := tempfile.Name()
|
||||
defer os.Remove(p)
|
||||
config := v1.Config{
|
||||
Clusters: []v1.NamedCluster{
|
||||
{
|
||||
Cluster: v1.Cluster{Server: callbackURL, CertificateAuthorityData: ca},
|
||||
},
|
||||
},
|
||||
AuthInfos: []v1.NamedAuthInfo{
|
||||
{
|
||||
AuthInfo: v1.AuthInfo{ClientCertificateData: clientCert, ClientKeyData: clientKey},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := json.NewEncoder(tempfile).Encode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tempconfigfile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pc := tempconfigfile.Name()
|
||||
defer os.Remove(pc)
|
||||
|
||||
configTmpl, err := template.New("testconfig").Parse(defaultConfigTmplYAML)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse test template: %v", err)
|
||||
}
|
||||
dataConfig := struct {
|
||||
KubeConfig string
|
||||
AllowTTL int64
|
||||
DenyTTL int64
|
||||
RetryBackoff int64
|
||||
DefaultAllow bool
|
||||
}{
|
||||
KubeConfig: p,
|
||||
AllowTTL: cacheTime.Nanoseconds(),
|
||||
DenyTTL: cacheTime.Nanoseconds(),
|
||||
RetryBackoff: 0,
|
||||
DefaultAllow: defaultAllow,
|
||||
}
|
||||
if err := configTmpl.Execute(tempconfigfile, dataConfig); err != nil {
|
||||
return nil, fmt.Errorf("failed to execute test template: %v", err)
|
||||
}
|
||||
|
||||
// Create a new admission controller
|
||||
configFile, err := os.Open(pc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read test config: %v", err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
wh, err := NewImagePolicyWebhook(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wh, err
|
||||
}
|
||||
|
||||
func TestTLSConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
clientCert, clientKey, clientCA []byte
|
||||
serverCert, serverKey, serverCA []byte
|
||||
wantAllowed, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "TLS setup between client and server",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: caCert,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth",
|
||||
clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not require client auth, client provides it",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Client does not trust server",
|
||||
clientCert: clientCert, clientKey: clientKey,
|
||||
serverCert: serverCert, serverKey: serverKey,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Server does not trust client",
|
||||
clientCert: clientCert, clientKey: clientKey, clientCA: caCert,
|
||||
serverCert: serverCert, serverKey: serverKey, serverCA: badCACert,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
// Plugin does not support insecure configurations.
|
||||
test: "Server is using insecure connection",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, tt.serverCert, tt.serverKey, tt.serverCA)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, tt.clientCert, tt.clientKey, tt.clientCA, -1, false)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
pod := goodPod(strconv.Itoa(rand.Intn(1000)))
|
||||
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
// Allow all and see if we get an error.
|
||||
service.Allow()
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit with AllowAll policy: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
service.Deny()
|
||||
if err := wh.Validate(attr); err == nil {
|
||||
t.Errorf("%s: incorrectly admitted with DenyAll policy", tt.test)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
type webhookCacheTestCase struct {
|
||||
statusCode int
|
||||
expectedErr bool
|
||||
expectedAuthorized bool
|
||||
expectedCached bool
|
||||
}
|
||||
|
||||
func testWebhookCacheCases(t *testing.T, serv *mockService, wh *Plugin, attr admission.Attributes, tests []webhookCacheTestCase) {
|
||||
for _, test := range tests {
|
||||
serv.statusCode = test.statusCode
|
||||
err := wh.Validate(attr)
|
||||
authorized := err == nil
|
||||
|
||||
if test.expectedErr && err == nil {
|
||||
t.Errorf("Expected error")
|
||||
} else if !test.expectedErr && err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if test.expectedAuthorized && !authorized {
|
||||
if test.expectedCached {
|
||||
t.Errorf("Webhook should have successful response cached, but authorizer reported unauthorized.")
|
||||
} else {
|
||||
t.Errorf("Webhook returned HTTP %d, but authorizer reported unauthorized.", test.statusCode)
|
||||
}
|
||||
} else if !test.expectedAuthorized && authorized {
|
||||
t.Errorf("Webhook returned HTTP %d, but authorizer reported success.", test.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebhookCache verifies that error responses from the server are not
|
||||
// cached, but successful responses are.
|
||||
func TestWebhookCache(t *testing.T) {
|
||||
serv := new(mockService)
|
||||
s, err := NewTestServer(serv, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
// Create an admission controller that caches successful responses.
|
||||
wh, err := newImagePolicyWebhook(s.URL, clientCert, clientKey, caCert, 200, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []webhookCacheTestCase{
|
||||
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 404, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 403, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 401, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
|
||||
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(goodPod("test"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
serv.allow = true
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, attr, tests)
|
||||
|
||||
// For a different request, webhook should be called again.
|
||||
tests = []webhookCacheTestCase{
|
||||
{statusCode: 500, expectedErr: true, expectedAuthorized: false, expectedCached: false},
|
||||
{statusCode: 200, expectedErr: false, expectedAuthorized: true, expectedCached: false},
|
||||
{statusCode: 500, expectedErr: false, expectedAuthorized: true, expectedCached: true},
|
||||
}
|
||||
attr = admission.NewAttributesRecord(goodPod("test2"), nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
testWebhookCacheCases(t, serv, wh, attr, tests)
|
||||
}
|
||||
|
||||
func TestContainerCombinations(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
pod *api.Pod
|
||||
wantAllowed, wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "Single container allowed",
|
||||
pod: goodPod("good"),
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "Single container denied",
|
||||
pod: goodPod("bad"),
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "One good container, one bad",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Multiple good containers",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "Multiple bad containers",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Good container, bad init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Bad container, good init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "bad",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "Good container, good init container",
|
||||
pod: &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
InitContainers: []api.Container{
|
||||
{
|
||||
Image: "good",
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantAllowed: true,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 200
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission: %s", tt.test)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission: %s", tt.test)
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAllow(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
pod *api.Pod
|
||||
wantAllowed, wantErr, defaultAllow bool
|
||||
}{
|
||||
{
|
||||
test: "DefaultAllow = true, backend unreachable, bad image",
|
||||
pod: goodPod("bad"),
|
||||
defaultAllow: true,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = true, backend unreachable, good image",
|
||||
pod: goodPod("good"),
|
||||
defaultAllow: true,
|
||||
wantAllowed: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = false, backend unreachable, good image",
|
||||
pod: goodPod("good"),
|
||||
defaultAllow: false,
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "DefaultAllow = false, backend unreachable, bad image",
|
||||
pod: goodPod("bad"),
|
||||
defaultAllow: false,
|
||||
wantAllowed: false,
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(mockService)
|
||||
service.statusCode = 500
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, tt.defaultAllow)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
attr := admission.NewAttributesRecord(tt.pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if tt.wantAllowed {
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("expected failed admission")
|
||||
}
|
||||
}
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("expected error making admission request: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to admit: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// A service that can record annotations sent to it
|
||||
type annotationService struct {
|
||||
annotations map[string]string
|
||||
}
|
||||
|
||||
func (a *annotationService) Review(r *v1alpha1.ImageReview) {
|
||||
a.annotations = make(map[string]string)
|
||||
for k, v := range r.Spec.Annotations {
|
||||
a.annotations[k] = v
|
||||
}
|
||||
r.Status.Allowed = true
|
||||
}
|
||||
func (a *annotationService) HTTPStatusCode() int { return 200 }
|
||||
func (a *annotationService) Annotations() map[string]string { return a.annotations }
|
||||
|
||||
func TestAnnotationFiltering(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
annotations map[string]string
|
||||
outAnnotations map[string]string
|
||||
}{
|
||||
{
|
||||
test: "all annotations filtered out",
|
||||
annotations: map[string]string{
|
||||
"test": "test",
|
||||
"another": "annotation",
|
||||
"": "",
|
||||
},
|
||||
outAnnotations: map[string]string{},
|
||||
},
|
||||
{
|
||||
test: "image-policy annotations allowed",
|
||||
annotations: map[string]string{
|
||||
"my.image-policy.k8s.io/test": "test",
|
||||
"other.image-policy.k8s.io/test2": "annotation",
|
||||
"test": "test",
|
||||
"another": "another",
|
||||
"": "",
|
||||
},
|
||||
outAnnotations: map[string]string{
|
||||
"my.image-policy.k8s.io/test": "test",
|
||||
"other.image-policy.k8s.io/test2": "annotation",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
// Use a closure so defer statements trigger between loop iterations.
|
||||
func() {
|
||||
service := new(annotationService)
|
||||
|
||||
server, err := NewTestServer(service, serverCert, serverKey, caCert)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create server: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
wh, err := newImagePolicyWebhook(server.URL, clientCert, clientKey, caCert, 0, true)
|
||||
if err != nil {
|
||||
t.Errorf("%s: failed to create client: %v", tt.test, err)
|
||||
return
|
||||
}
|
||||
|
||||
pod := goodPod("test")
|
||||
pod.Annotations = tt.annotations
|
||||
|
||||
attr := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "namespace", "", api.Resource("pods").WithVersion("version"), "", admission.Create, &user.DefaultInfo{})
|
||||
|
||||
err = wh.Validate(attr)
|
||||
if err != nil {
|
||||
t.Errorf("expected successful admission")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tt.outAnnotations, service.Annotations()) {
|
||||
t.Errorf("expected annotations sent to webhook: %v to match expected: %v", service.Annotations(), tt.outAnnotations)
|
||||
}
|
||||
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func goodPod(containerID string) *api.Pod {
|
||||
return &api.Pod{
|
||||
Spec: api.PodSpec{
|
||||
ServiceAccountName: "default",
|
||||
SecurityContext: &api.PodSecurityContext{},
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Image: containerID,
|
||||
SecurityContext: &api.SecurityContext{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
211
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/certs_test.go
generated
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// This file was generated using openssl by the gencerts.sh script
|
||||
// and holds raw certificates for the imagepolicy webhook tests.
|
||||
|
||||
package imagepolicy
|
||||
|
||||
var caKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAoKjaP9PtRAGRNCx8z+0LTGt2eEduqElcPrm8EvlBwn3dnLFo
|
||||
55x+Tejb6ysQsyy1BKI0dRdX4tNSAgFFFaIVcsOo9kGtPq7QsSd4VWViNE3L5zJA
|
||||
+0X2ztHBkPlQXwDrtArsNKxwcpyHP9sXE05BN36XBjAz2XkusTkFrdJ/PzjZhlb4
|
||||
9i9gTZ0bJbexQ1+dfZX2WpY70JypYnKrbV1dLj5ORb65SC8IWZcG/ouqLWAN+lT+
|
||||
eug8P6PjoOQWs3qsl0bSAtAdiYcwXKtPiBEWPJe24ACywyE+8jVzmIJqAm0U1V8k
|
||||
GTHzjmSRwzgX/VN5JMri/nxNIW5UsbhHzYHfjQIDAQABAoIBAQCIeAWz1Bwl+ULT
|
||||
U7rNkChZyKrAbsUDdBVEPtcQMuR2Bh5Z/KUEoHz1RwiP0WwFFsPI5NO0ZpjD1wdB
|
||||
Jrz9LEoVyzfZvl4f8bTZ1pIzz8PEdBTxFVH3Xy3P7oMC15Q6rviIXgLYl2WJJYcJ
|
||||
adxHDOD+96vnmMhiQbq01aAKT9TA6PvXXDusfadMQ+il+mEbeZz4aNYBk9u+34Co
|
||||
aQTNwlLft5anW2820IMJdJR/bFjyX71cPID1rIjw4VOQZExIpIEnuHPiulyE4EvJ
|
||||
hvvVKAm0dRjHg39cz0eAQ6PntX3DUvjNfcLLrj7sQxLco1cnAKZxhpZ8ajtvynr5
|
||||
pF2d5xYBAoGBAM8y/e5+raHTLHEKZUc0vekUey3fc4aRqptyAKTS0ZvOYBXg4Vhl
|
||||
mOK7066IEqwF4UHGmQqW6D5HstqPGx0uN0d9IyImUqDp0JotdFSZMEMQkYLyFD+r
|
||||
J7O2nOO6E4SOxXO9/q9iSB+G/qgl6LS3O9+58uHTYEbUommiDZ6a18qBAoGBAMZ/
|
||||
xSGMa3b6vrU3rUTEh+xBh6YRVNYAxWwpGg2sO0k2brT3SxSMCrx1wvNGY+k7XNx0
|
||||
JJfZQDC/wlR0rcVTnPCi/cE9FTUlh23xXCPRlxwc4vLly+7yU95LhAO+N9XAwsrs
|
||||
OIi4lR57jxoLNO2ofoAVMvllkE5Eo5W6lOPR2xcNAoGAV1Tv0OFV//pJJhAypfOm
|
||||
BCLc1HX1dIfbOA+yE8bEEH7I4w/ZC3AvI4n1a//wls8Xpai2gs8ebnm7+gENdZww
|
||||
MpKdB1zNwQMsKH/2I146CFpoap/sRvW2EzpqIFYiueGPefxf575uFdPJbEgmMF13
|
||||
ABKZO/PjBZfEKO/j+7DaOYECgYBYX+Zqa1QlIrnpgKJZ7Y3+d6ZnH2w/4xQCdcIt
|
||||
uDKlA+ECHN+GhFr7UQq8uOgenNlZJTRtjsHvclCYvWHoarOCx25mrEVW5iCHqF+3
|
||||
asb2Mz4vmnPTLHx+iex6piPBvRJ8ufLpnBR3/9bUZ4znCo9XgxiwxLEcx551OR60
|
||||
12fNuQKBgC1fkqgtDDxQzrabSmmiqXthcPXxFdsYqnSNlFgba0uaAp9LREztSrX8
|
||||
QhwSoSwHVmjBvR6SybLYdsZ9Efj/w7XBejOOcS44MOoHYYFdsP7W47Ao5QFqvDoI
|
||||
oqyQ1R73cF9WX6obRQwH4P3DvcsBebOjvjMX9mljKtpJMc9KqrGc
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var caCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJAJlL10mfdZraMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgqNo/0+1EAZE0LHzP7QtM
|
||||
a3Z4R26oSVw+ubwS+UHCfd2csWjnnH5N6NvrKxCzLLUEojR1F1fi01ICAUUVohVy
|
||||
w6j2Qa0+rtCxJ3hVZWI0TcvnMkD7RfbO0cGQ+VBfAOu0Cuw0rHBynIc/2xcTTkE3
|
||||
fpcGMDPZeS6xOQWt0n8/ONmGVvj2L2BNnRslt7FDX519lfZaljvQnKlicqttXV0u
|
||||
Pk5FvrlILwhZlwb+i6otYA36VP566Dw/o+Og5BazeqyXRtIC0B2JhzBcq0+IERY8
|
||||
l7bgALLDIT7yNXOYgmoCbRTVXyQZMfOOZJHDOBf9U3kkyuL+fE0hblSxuEfNgd+N
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBSx2m5pJoFpdGDmOzSVl29jkheQFTAfBgNVHSME
|
||||
GDAWgBSx2m5pJoFpdGDmOzSVl29jkheQFTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBe6tZzmOQKt8fTsnDDKvEjSwK2Pb91R5tkwmIhdpTjmAgC+Zkk
|
||||
kSihR9sZIxdRC4wlbuorRl8BjhX5I8Kr3FWdDhOrIhicp7CIrxPiFh6+ZLSOj3o9
|
||||
pQ6SriIopjXCHvl5XjzKxLg/uQpzui/YUtfqffCRB4EccOsjlyUanK5rjMLBMLCn
|
||||
2LadiRB2Q/cC9fYigczETACDjq5vzp6I9eqwpCTmv/+4bFncW+VBD4touaJc8FKf
|
||||
ljW5xekKRh4uzP85X7rEgrFen/my5Fs/cylkFvYIiZwgn6NLgW3BNi+m31XIfU0S
|
||||
xIbgh4UH0dwc6Zk8WUwFud4GXj6OyGneMGKB
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var badCAKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAnKpC89q4/H+Xg91xI+GLhkrpJrO4n3nw0+/EQUoF9qwLtEDk
|
||||
mJp6ymUulwJgfvJwHOsUYqQB6jMKfXyqeSR24ssjjF9LTKhaQMZOGcW5Mshi04Ie
|
||||
USX93wDZwbWwqihVSqWaMpmf3JByeldnXNtc29Ik6NwqZcNWW5kEsSszheLhOU4i
|
||||
ZcRUlovwMYhHX37vQCQ1aygMaIMgBOb/vogSNxumqPKS4WdWsjss6LmEPnm350e+
|
||||
+9cb6RAfrDlOaj32VbLEp500SfBpZeCuyc5v81X12HT4V0qmsqIZ79tIgrqAaPxE
|
||||
D/HJXPpH64EAr23bR9dMPLXTh6w2yYWu+NGYywIDAQABAoIBAQCE/CZPN1gVxf0I
|
||||
i12x9o/oVAhruN08Sld6oCm4viwnws1AmmExhNg8m/0bZIIi4Ir4kThBrzSM5/y8
|
||||
nqlaofBk/cjULEQP80yBdZPwXp2hlOYG4on3mkdRGDjALQmktw4HimFFGJDRuq/i
|
||||
V/U+plrBojWAkPtQXKsen9qSxbg7qhI6KZyUQKExIHhsCfmE1ZzGx+/bgLVJEagi
|
||||
7zzZdAj2BzdoCk8yySAAsZG+pNSnd8gs5EzzRJ1RXanwxPSeEG/guX9YhLgLhhFu
|
||||
XzXngJDKVVhz4F2TfxtqIvZYvTMNh0R1OE0OUO2P88M837KKk5BHvW9oqYKZTUFV
|
||||
MC9k5No5AoGBAMtUBp8UcYZy+yetOAK2iGaEYwuWx8vwjY0c1POWun2Hny0nYxTQ
|
||||
WxXXqKaJydxZ+DlD3XuRKmMlKZQsp+bzuL5ukWN/ipO5tgQQfuKOZqVwvL19GkFi
|
||||
+Qr70G/TvYT/rv6A4s6XqbG4xt+7c2gf/XSghyoIyq1uwOcNNtrMdM/tAoGBAMU/
|
||||
tYc4d+vAl7hd8TwhFiZiC3N84C1HwsPVj38uqQI/j8boB21Bhpw6HHzq+VdVPfvp
|
||||
zk5e8AiQdSpitM7pBVmLpoRdTQjdlUDFRUi4TdJwfp5P7dXM8D6swNQ9f9w180na
|
||||
5ewu16PSC+sh19wAl04KwOmiDqZujJrBgWnFcESXAoGBALGofoybAUK3zqlxWcJN
|
||||
GUtyG1Sx72tLiXMmIQ+hwNsUGEoM4y75isy//ZVeSammVxQ6Lxjb00yD2RumFSLg
|
||||
C6kg1Ro6A6xmFRriCuwL/rZJljB/UeSWBQLK2eoL+clu2sl3djWLIPOvft1YXVM6
|
||||
uGwiI1fgDK+TWSvJSQfOo7ZVAoGBAK+A6DvQeqNBUb2xmJsvtU2hnx661Zx0ZU9q
|
||||
DavUEHz3oS4R9cm4q9UFv6NGT2Tta6FhfzcsMdbs8dMs0EPqAeCS6S6M9aYVwl9H
|
||||
J0Z09olvnrmt1KiPGJQrkcdGkSWWu0nTgxCK/UO9+OzVyALwY7AE0XEPyIk9g82O
|
||||
r181VZcxAoGANY2QGYrNtfa++o2B0O4qskKxhYEeCnZPptmjVO0oHOx2YSDQXK3K
|
||||
B0evCQ7ylvMnobNLjp9bqD14a0M86QjRlpSg1vHUhBsETZICc+E0UgV28CdWgYtt
|
||||
urARDE9ZpLVSRfPVAitC1I76pZwevsbQ9TeS2p0cWQpYYKmBtGpkdug=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var badCACert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDFzCCAf+gAwIBAgIJANQEJyMW4HFZMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCExHzAdBgNVBAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2Ew
|
||||
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcqkLz2rj8f5eD3XEj4YuG
|
||||
Sukms7ifefDT78RBSgX2rAu0QOSYmnrKZS6XAmB+8nAc6xRipAHqMwp9fKp5JHbi
|
||||
yyOMX0tMqFpAxk4ZxbkyyGLTgh5RJf3fANnBtbCqKFVKpZoymZ/ckHJ6V2dc21zb
|
||||
0iTo3Cplw1ZbmQSxKzOF4uE5TiJlxFSWi/AxiEdffu9AJDVrKAxogyAE5v++iBI3
|
||||
G6ao8pLhZ1ayOyzouYQ+ebfnR7771xvpEB+sOU5qPfZVssSnnTRJ8Gll4K7Jzm/z
|
||||
VfXYdPhXSqayohnv20iCuoBo/EQP8clc+kfrgQCvbdtH10w8tdOHrDbJha740ZjL
|
||||
AgMBAAGjUDBOMB0GA1UdDgQWBBRjFVG818hHK+HSEhdz+gPwSKa4kzAfBgNVHSME
|
||||
GDAWgBRjFVG818hHK+HSEhdz+gPwSKa4kzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQBBCl0UJq0iLy/dvym79mnoPZ1KPhS2WnQB5ZLzJwL26ePkr8j8
|
||||
G/1AOVPu73hovJx51b+T7ZhTgtmAEwqpRHBxRQ0+Yf973YOVJYp4QFGWDnueurzv
|
||||
bCsnZEPkQtccHzZxT3fUsM6Ejy99j0WBNmvfAj1X7yNaN5EZw6kvuaDDda3I7WNM
|
||||
0eGy8aoAcPJZkYfZb39VDq/qJn+bVsAJdUaXt/FkDZBJl6XzoGjC/webjRJOpkgN
|
||||
vgjJDhhQ8LlHFiq+lXIiK4Y55RBWG3iXGTM8W3fjZYTNvH7FlGyuRD4Y4hyaYXTP
|
||||
+PoFWuDZM89EAyICr0yyTc8mkdrAEM/Lj9GO
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var serverKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAyhmjG7BJCGwuf1FyHJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTH
|
||||
riPOe9d1ahH7bvsZycnzh7/pABdTDUdStiR8/1KUYt8PjjosrmmYyupqNPq+wkBD
|
||||
EmKa+4voR2EBgXbIGghx8e++KmmNnSCNk6B8m2EJR0fn9zPnoY3uHNogKjCICt19
|
||||
g+uipuwZco7yTu3e40LwpIVmA8SsrM0S/CaZqSmtIClSwv7YDvreUd6FuI/GT0cj
|
||||
NMPRuSdfohBxGz6R7Cml7qP4AYKajjl+08mRYv3o+hVclXUltcRmTnJanYGmGS3k
|
||||
C7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS0PhjmQIDAQABAoIBAFOicmZ1+HM82a0k
|
||||
llWSV5xPUzUmU6TT4bJlZnzJd0R7i+6H8250MPH9AwEHOgb+cPiZ02cdGx5HiL4Z
|
||||
AviPdw7uLKwR5U0VdAIlfu6SPat5DNI0Z81G8x4gEtrfIRFjh4GGdykI7qh8j/cz
|
||||
ToOGSaq/aGiQMEWTvEqWArD7742lVHE4/1bM3GuKV8shy31zfw0d9RCCy1GdBR75
|
||||
zZ1w4zKL55DM3PC73Ndy2IcrViVXVAgfqD0xxKwQW1qoENgThueALj3PkU1XaKxI
|
||||
nOdztt1fBFpcSHyFBkJ1sexumnssMRXSVcJ/0D5F2T4QPUnWBM0oSzoyioAab4RP
|
||||
8XrZwAECgYEA/eFjNgCeHztXgS3YRC/RddLOtobrerYKN7vA64ou5VUCqEQ9rfQE
|
||||
MbmKdZdiFVNJI0JrPq8Gx39ME9g2OLTVVqdtlm6JYjy5CHdUXHIHObo9oz7Uueos
|
||||
TdeCf0LFvEUNXvbGIP5KqcdVi+wekauHMqXGQYTNa6bar/FE99MdyAECgYEAy8mU
|
||||
tCjm4QsuKsdku5bDHGv56ZN9DkWd7Lcjie5otElwH9bKfIQ2lUYyoUAIa0rEJ9Ya
|
||||
7vuAZ2bX7od9s8Jkci91ONDWxdy361SRZcbpuqgQKKVRuzGlfamufyW4sStbXY1k
|
||||
+zeQxyWGJHhhLWpapzca89RELGZSkbIMVVIT25kCgYEA7EUYboZuoYQ5cGf476RM
|
||||
28kfRXEUrvPBWJLr/IhyEk1mFrDDciM40AnrWHpU9qG23BCQ/BopRforFADQnT91
|
||||
l5pje29NfdYjIUTkhtA79zZi7IyprofHSX453TOIECl3QxyH0Oa3F4ACFiDdZhXq
|
||||
0XDDq+/quLfkp37y/2xDOAECgYEAmi55g5UumTWMSHFzlToLhIVtH3unMhUZ1u74
|
||||
xHLMZRrq6ivoJy0g3u+tfrKjrAl1P26OEiHWlGULGj0Ireh1dq7RUZsv46OKw1HI
|
||||
b+h/Den5z8bEf4ygWOL4UtqHUgQrrCw+KpNvxjxtsUoiu+mrjLf0fGYs7iq8bd73
|
||||
1dWzkIECgYEAi6P/LzMC6orbyONmwlscqO1Ili8ZBkUjJ/wThkiNMMA3pyKmb68W
|
||||
yt56Yh0rs+WnuVUN90cG87k+CY35dQ7FAOVUJi9LWGA3Oq9fGkoOB7f4dzaUu/rB
|
||||
dtit2KPCxiKpZsxqSf4+S8AXYF48abNPLYK3DCCSqAah09gYOrqYlW4=
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var serverCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIDCzCCAfOgAwIBAgIJAMvo2rkGpEUQMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfc2Vy
|
||||
dmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyhmjG7BJCGwuf1Fy
|
||||
HJtq9iUXZ3oymtrOHdaSAcsCSxFrUJTHriPOe9d1ahH7bvsZycnzh7/pABdTDUdS
|
||||
tiR8/1KUYt8PjjosrmmYyupqNPq+wkBDEmKa+4voR2EBgXbIGghx8e++KmmNnSCN
|
||||
k6B8m2EJR0fn9zPnoY3uHNogKjCICt19g+uipuwZco7yTu3e40LwpIVmA8SsrM0S
|
||||
/CaZqSmtIClSwv7YDvreUd6FuI/GT0cjNMPRuSdfohBxGz6R7Cml7qP4AYKajjl+
|
||||
08mRYv3o+hVclXUltcRmTnJanYGmGS3kC7KiE2sHINzF2qUUAoO+yXpMx7QtK+NS
|
||||
0PhjmQIDAQABo0AwPjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDATAPBgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCF
|
||||
xaS/KIijKDLbaL/P7AxhnAta8jYSEzL66WTaYV4GeRhLtX/vPUV9gzPWnkNr0TBM
|
||||
lS+Q0KDxh17rJ/MrWwrMSwsgKZahTR+7mSHiXrIlHcnHXXSvhnoXu8VDu8goqOEI
|
||||
5yRHt6plzmFZEwVi/hSmIAuQjmyjOk2dc/ZKI0fMExKhnVms8AoztjAMbt3TFMTK
|
||||
Kk7bVGPblFsXiVPhRlzbLbh5i/PvHHf+12ACrVxoxOOQUmuXy1DPxmkk7jP3FIsE
|
||||
+rnyWnfmGS5sW8oMkj2nFYIh3LehADsMS9s7JVlJk/loNJDA9Yn2fev/vRKck8RZ
|
||||
siw54G4e+6nKpY5BAY1M
|
||||
-----END CERTIFICATE-----`)
|
||||
|
||||
var clientKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA3IOqCz88jTQpsGIBFTdjbqBg+0NFeym3OEl8zLfzkLQuZieO
|
||||
3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWIY8KU7c2SPfErlhP86VFoD0RKHJxwRVh0
|
||||
y70WyK8+CzzwrrPpWydgtAwbm9F+0v/zdcCL0TEL2/MYgCc97mSGwtTRaW4bqq6V
|
||||
MWMHBcOu44dHq8+CF8ixxk0WSBl2oocXnF7QdEA15iuOM5hacLB0fyH4T3NM54lO
|
||||
rOSXUMUuysougSrMcCPv3esFlv4TVUkldwu73jWx+Wja0gNXlnmgU2lqFdM+PsVT
|
||||
DPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkNcx9QDQIDAQABAoIBADblRCC2pmFUmghB
|
||||
7ZkVh9hTbrE+Zv6pPOZzTPE93hGo+WAO+v6GNBLuIEte87DhF2QTmovp4VfsFeXK
|
||||
oECNgTvOEFkBP+OFqFGBJZGfY3/J5h0tTy4lLZXaImzzx8sGGNLLc8R+uyTIO3VV
|
||||
qIso2uXB+vzPgMrueflt5yp7hoJjI0c+qEktUg5n+WJFAFteI9LCngN+xwRWVEgp
|
||||
rjKVPcT9zio8tLJOhcSPA7q6lORUkwbPWHyNDpamvldnqjhgp5Ceq5f/qfoWPzvM
|
||||
H5o72Ax2WduxST+P+hCOqZReUmTaGzAKb5rJwdEpmbnDZ3kSR08aT/40m/EG1SvQ
|
||||
pi0b3QECgYEA/mRGIjaYPQr+tw3Sz8g76t3PYfrglro60HdLBn2IUpj2sEpazNId
|
||||
2aPFPb58whL+VPmUfXbpPH+wW/+wWpRw4MraFkJanbOjDiEGXK5ZoUQIDZJWUSwf
|
||||
oCge5uacU69weC67UyPYmK1e+A/gaFw1Dz729jLxtB3rGWKxEGbWEc0CgYEA3eiP
|
||||
hv0GxbdEEbSfQoSPKbBHGI9spaqAIcqL+dSsx3m6Ckqx0El/xi9mQkITgqs2gyqI
|
||||
o2T/3yDli9oF4+3Plz0wrZ11auOWX+nhKfACtF679I1PL0UOavXF0FVgOfwOIqdG
|
||||
jp4QQV7USkbTP9ZOHo90Y8G4rmTEdMZ/VsH490ECgYEA8u/bsiyk8haf7Tx8SAWW
|
||||
gtLUi2NEO20ZYZ+qvEYBe6+sVeqMD/HQo9ksMazKA6ST0Z6O2cpHLolaaGEjjz0X
|
||||
FvVhk8RGOTglzQZoxvWRjtojPqKzX81dXlsyN5ufSqPOKlemeN1QqW1XtlmjGsaD
|
||||
vU2KFs/L1xCDRbjkEx/B6zkCgYBmqeE9InKvpknnpxjHPWy+bL93rWMmgesltv9r
|
||||
ZelJoBdiC4yYQGjM18EHhmpgWbWumU79yQxXvnB0czmmaa9Q2Q5cRCy+duxrE1kI
|
||||
ffHCYNG0ImwwAlLZSTtrVxRdvy8K+Ti7YoVCuQyeEIZLUmpx2QyP2mAGzrfVDsB6
|
||||
8uKsAQKBgQDO+PmADra91NKJP1iVuvOK8iEy/Z14L03uKtF3X9u8vLdzQZa/Q/P9
|
||||
hXOX9ovFwSBQOOfgb+/+QRuPL4xxi1J8CFwrSWCEeFgrDijl9DS6aNY6BWHDA8p6
|
||||
8V7Adb04cnenj8QjYYN8/mqsQlHSoAIxeAlUoJpq+pk7O8PAfbjgMw==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
var clientCert = []byte(`-----BEGIN CERTIFICATE-----
|
||||
MIIC+jCCAeKgAwIBAgIJAMvo2rkGpEURMA0GCSqGSIb3DQEBCwUAMCExHzAdBgNV
|
||||
BAMMFndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2EwIBcNMTYwODEyMjAyMjUwWhgPMjI5
|
||||
MDA1MjgyMDIyNTBaMCUxIzAhBgNVBAMUGndlYmhvb2tfaW1hZ2Vwb2xpY3lfY2xp
|
||||
ZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3IOqCz88jTQpsGIB
|
||||
FTdjbqBg+0NFeym3OEl8zLfzkLQuZieO3AoFMiLaeYgC4m9BBsdJWSXRzWcqgVWI
|
||||
Y8KU7c2SPfErlhP86VFoD0RKHJxwRVh0y70WyK8+CzzwrrPpWydgtAwbm9F+0v/z
|
||||
dcCL0TEL2/MYgCc97mSGwtTRaW4bqq6VMWMHBcOu44dHq8+CF8ixxk0WSBl2oocX
|
||||
nF7QdEA15iuOM5hacLB0fyH4T3NM54lOrOSXUMUuysougSrMcCPv3esFlv4TVUkl
|
||||
dwu73jWx+Wja0gNXlnmgU2lqFdM+PsVTDPMzoHTEhIGPIWO5anYR5Qv0SmX3nXkN
|
||||
cx9QDQIDAQABoy8wLTAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
|
||||
BggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAkHIhrPfRROhzLg2hRZz5/7Kw
|
||||
3V0/Y0XS91YU3rew+c2k++bLp1INzpWxfB6gbSC6bTOgn/seIDvxwJ2g5DRdOxU/
|
||||
Elcpqg1hTCVfpmra9PCniMzZuP7lsz8sJKj6FgE6ElJ1S74FW/CYz/jA+76LLot4
|
||||
JwGkCJHzyLgFPBEOjJ/mLYSM/SDzHU5E+NHXVaKz4MjM3JwycN/juqi4ikAcZEBW
|
||||
1HmpcHKBedAwlCM90zlvG2SL4sFRp/clMbntRdmh5L+/1F6aP82PO3iuvXtXP48d
|
||||
NtjboxP3IV2eY5iUle8BOQ9CnFQs4wsF1LxTMNACypQyFinMsHrCpwrB3i4VvA==
|
||||
-----END CERTIFICATE-----`)
|
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
93
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config.go
generated
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2016 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 imagepolicy contains an admission controller that configures a webhook to which policy
|
||||
// decisions are delegated.
|
||||
package imagepolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultRetryBackoff = time.Duration(500) * time.Millisecond
|
||||
minRetryBackoff = time.Duration(1)
|
||||
maxRetryBackoff = time.Duration(5) * time.Minute
|
||||
defaultAllowTTL = time.Duration(5) * time.Minute
|
||||
defaultDenyTTL = time.Duration(30) * time.Second
|
||||
minAllowTTL = time.Duration(1) * time.Second
|
||||
maxAllowTTL = time.Duration(30) * time.Minute
|
||||
minDenyTTL = time.Duration(1) * time.Second
|
||||
maxDenyTTL = time.Duration(30) * time.Minute
|
||||
useDefault = time.Duration(0) //sentinel for using default TTL
|
||||
disableTTL = time.Duration(-1) //sentinel for disabling a TTL
|
||||
)
|
||||
|
||||
// imagePolicyWebhookConfig holds config data for imagePolicyWebhook
|
||||
type imagePolicyWebhookConfig struct {
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
AllowTTL time.Duration `json:"allowTTL"`
|
||||
DenyTTL time.Duration `json:"denyTTL"`
|
||||
RetryBackoff time.Duration `json:"retryBackoff"`
|
||||
DefaultAllow bool `json:"defaultAllow"`
|
||||
}
|
||||
|
||||
// AdmissionConfig holds config data for admission controllers
|
||||
type AdmissionConfig struct {
|
||||
ImagePolicyWebhook imagePolicyWebhookConfig `json:"imagePolicy"`
|
||||
}
|
||||
|
||||
func normalizeWebhookConfig(config *imagePolicyWebhookConfig) (err error) {
|
||||
config.RetryBackoff, err = normalizeConfigDuration("backoff", time.Millisecond, config.RetryBackoff, minRetryBackoff, maxRetryBackoff, defaultRetryBackoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.AllowTTL, err = normalizeConfigDuration("allow cache", time.Second, config.AllowTTL, minAllowTTL, maxAllowTTL, defaultAllowTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.DenyTTL, err = normalizeConfigDuration("deny cache", time.Second, config.DenyTTL, minDenyTTL, maxDenyTTL, defaultDenyTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeConfigDuration(name string, scale, value, min, max, defaultValue time.Duration) (time.Duration, error) {
|
||||
// disable with -1 sentinel
|
||||
if value == disableTTL {
|
||||
glog.V(2).Infof("image policy webhook %s disabled", name)
|
||||
return time.Duration(0), nil
|
||||
}
|
||||
|
||||
// use default with 0 sentinel
|
||||
if value == useDefault {
|
||||
glog.V(2).Infof("image policy webhook %s using default value", name)
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
// convert to s; unmarshalling gives ns
|
||||
value *= scale
|
||||
|
||||
// check value is within range
|
||||
if value < min || value > max {
|
||||
return value, fmt.Errorf("valid value is between %v and %v, got %v", min, max, value)
|
||||
}
|
||||
return value, nil
|
||||
}
|
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
133
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/config_test.go
generated
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2016 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 imagepolicy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestConfigNormalization(t *testing.T) {
|
||||
tests := []struct {
|
||||
test string
|
||||
config imagePolicyWebhookConfig
|
||||
normalizedConfig imagePolicyWebhookConfig
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
test: "config within normal ranges",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second,
|
||||
RetryBackoff: ((minRetryBackoff + maxRetryBackoff) / 2) / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: ((minAllowTTL + maxAllowTTL) / 2) / time.Second * time.Second,
|
||||
DenyTTL: ((minDenyTTL + maxDenyTTL) / 2) / time.Second * time.Second,
|
||||
RetryBackoff: (minRetryBackoff + maxRetryBackoff) / 2,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config below normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL - time.Duration(1),
|
||||
DenyTTL: minDenyTTL - time.Duration(1),
|
||||
RetryBackoff: minRetryBackoff - time.Duration(1),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config above normal ranges, error",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(1) + maxAllowTTL,
|
||||
DenyTTL: time.Duration(1) + maxDenyTTL,
|
||||
RetryBackoff: time.Duration(1) + maxRetryBackoff,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
test: "config wants default values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: useDefault,
|
||||
DenyTTL: useDefault,
|
||||
RetryBackoff: useDefault,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: defaultAllowTTL,
|
||||
DenyTTL: defaultDenyTTL,
|
||||
RetryBackoff: defaultRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config wants disabled values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: disableTTL,
|
||||
DenyTTL: disableTTL,
|
||||
RetryBackoff: disableTTL,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: time.Duration(0),
|
||||
DenyTTL: time.Duration(0),
|
||||
RetryBackoff: time.Duration(0),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config within normal ranges for min values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL / time.Second,
|
||||
DenyTTL: minDenyTTL / time.Second,
|
||||
RetryBackoff: minRetryBackoff,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: minAllowTTL,
|
||||
DenyTTL: minDenyTTL,
|
||||
RetryBackoff: minRetryBackoff * time.Millisecond,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
test: "config within normal ranges for max values",
|
||||
config: imagePolicyWebhookConfig{
|
||||
AllowTTL: maxAllowTTL / time.Second,
|
||||
DenyTTL: maxDenyTTL / time.Second,
|
||||
RetryBackoff: maxRetryBackoff / time.Millisecond,
|
||||
},
|
||||
normalizedConfig: imagePolicyWebhookConfig{
|
||||
AllowTTL: maxAllowTTL,
|
||||
DenyTTL: maxDenyTTL,
|
||||
RetryBackoff: maxRetryBackoff,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
err := normalizeWebhookConfig(&tt.config)
|
||||
if err == nil && tt.wantErr == true {
|
||||
t.Errorf("%s: expected error from normalization and didn't have one", tt.test)
|
||||
}
|
||||
if err != nil && tt.wantErr == false {
|
||||
t.Errorf("%s: unexpected error from normalization: %v", tt.test, err)
|
||||
}
|
||||
if err == nil && !reflect.DeepEqual(tt.config, tt.normalizedConfig) {
|
||||
t.Errorf("%s: expected config to be normalized. got: %v expected: %v", tt.test, tt.config, tt.normalizedConfig)
|
||||
}
|
||||
}
|
||||
}
|
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
Copyright 2016 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 imagepolicy checks a webhook for image admission
|
||||
package imagepolicy // import "k8s.io/kubernetes/plugin/pkg/admission/imagepolicy"
|
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
102
vendor/k8s.io/kubernetes/plugin/pkg/admission/imagepolicy/gencerts.sh
generated
vendored
Executable file
@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2016 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.
|
||||
|
||||
set -e
|
||||
|
||||
# gencerts.sh generates the certificates for the webhook authz plugin tests.
|
||||
#
|
||||
# It is not expected to be run often (there is no go generate rule), and mainly
|
||||
# exists for documentation purposes.
|
||||
|
||||
cat > server.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
IP.1 = 127.0.0.1
|
||||
EOF
|
||||
|
||||
cat > client.conf << EOF
|
||||
[req]
|
||||
req_extensions = v3_req
|
||||
distinguished_name = req_distinguished_name
|
||||
[req_distinguished_name]
|
||||
[ v3_req ]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
extendedKeyUsage = clientAuth
|
||||
EOF
|
||||
|
||||
# Create a certificate authority
|
||||
openssl genrsa -out caKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key caKey.pem -days 100000 -out caCert.pem -subj "/CN=webhook_imagepolicy_ca"
|
||||
|
||||
# Create a second certificate authority
|
||||
openssl genrsa -out badCAKey.pem 2048
|
||||
openssl req -x509 -new -nodes -key badCAKey.pem -days 100000 -out badCACert.pem -subj "/CN=webhook_imagepolicy_ca"
|
||||
|
||||
# Create a server certiticate
|
||||
openssl genrsa -out serverKey.pem 2048
|
||||
openssl req -new -key serverKey.pem -out server.csr -subj "/CN=webhook_imagepolicy_server" -config server.conf
|
||||
openssl x509 -req -in server.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out serverCert.pem -days 100000 -extensions v3_req -extfile server.conf
|
||||
|
||||
# Create a client certiticate
|
||||
openssl genrsa -out clientKey.pem 2048
|
||||
openssl req -new -key clientKey.pem -out client.csr -subj "/CN=webhook_imagepolicy_client" -config client.conf
|
||||
openssl x509 -req -in client.csr -CA caCert.pem -CAkey caKey.pem -CAcreateserial -out clientCert.pem -days 100000 -extensions v3_req -extfile client.conf
|
||||
|
||||
outfile=certs_test.go
|
||||
|
||||
cat > $outfile << EOF
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
EOF
|
||||
|
||||
echo "// This file was generated using openssl by the gencerts.sh script" >> $outfile
|
||||
echo "// and holds raw certificates for the imagepolicy webhook tests." >> $outfile
|
||||
echo "" >> $outfile
|
||||
echo "package imagepolicy" >> $outfile
|
||||
for file in caKey caCert badCAKey badCACert serverKey serverCert clientKey clientCert; do
|
||||
data=$(cat ${file}.pem)
|
||||
echo "" >> $outfile
|
||||
echo "var $file = []byte(\`$data\`)" >> $outfile
|
||||
done
|
||||
|
||||
# Clean up after we're done.
|
||||
rm *.pem
|
||||
rm *.csr
|
||||
rm *.srl
|
||||
rm *.conf
|
71
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/BUILD
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/BUILD
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/initialresources",
|
||||
library = ":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"],
|
||||
)
|
218
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission.go
generated
vendored
Normal file
218
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission.go
generated
vendored
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
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
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
// WARNING: this feature is experimental and will definitely change.
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("InitialResources", 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
|
||||
}
|
300
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission_test.go
generated
vendored
Normal file
300
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
||||
/*
|
||||
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])
|
||||
}
|
56
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/data_source.go
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/data_source.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
/*
|
||||
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)
|
||||
}
|
45
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/data_source_test.go
generated
vendored
Normal file
45
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/data_source_test.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
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")
|
||||
}
|
||||
}
|
132
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/gcm.go
generated
vendored
Normal file
132
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/gcm.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
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
|
||||
}
|
46
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/gcm_test.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/gcm_test.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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")
|
||||
}
|
||||
}
|
223
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular.go
generated
vendored
Normal file
223
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular.go
generated
vendored
Normal file
@ -0,0 +1,223 @@
|
||||
/*
|
||||
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
|
||||
}
|
142
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular_test.go
generated
vendored
Normal file
142
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/hawkular_test.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
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
|
||||
}
|
73
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/influxdb.go
generated
vendored
Normal file
73
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/influxdb.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
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
|
||||
}
|
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/influxdb_test.go
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/plugin/pkg/admission/initialresources/influxdb_test.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
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")
|
||||
}
|
||||
}
|
65
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/BUILD
generated
vendored
Normal file
65
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/BUILD
generated
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
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",
|
||||
"interfaces.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/limitranger",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta: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/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/limitranger",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality: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/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
595
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission.go
generated
vendored
Normal file
595
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission.go
generated
vendored
Normal file
@ -0,0 +1,595 @@
|
||||
/*
|
||||
Copyright 2014 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 limitranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
limitRangerAnnotation = "kubernetes.io/limit-ranger"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("LimitRanger", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewLimitRanger(&DefaultLimitRangerActions{})
|
||||
})
|
||||
}
|
||||
|
||||
// LimitRanger enforces usage limits on a per resource basis in the namespace
|
||||
type LimitRanger struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
actions LimitRangerActions
|
||||
lister corelisters.LimitRangeLister
|
||||
|
||||
// liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
|
||||
// This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
|
||||
// We track the lookup result here so that for repeated requests, we don't look it up very often.
|
||||
liveLookupCache *lru.Cache
|
||||
liveTTL time.Duration
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &LimitRanger{}
|
||||
var _ admission.ValidationInterface = &LimitRanger{}
|
||||
var _ kubeapiserveradmission.WantsInternalKubeInformerFactory = &LimitRanger{}
|
||||
|
||||
type liveLookupEntry struct {
|
||||
expiry time.Time
|
||||
items []*api.LimitRange
|
||||
}
|
||||
|
||||
func (l *LimitRanger) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
limitRangeInformer := f.Core().InternalVersion().LimitRanges()
|
||||
l.SetReadyFunc(limitRangeInformer.Informer().HasSynced)
|
||||
l.lister = limitRangeInformer.Lister()
|
||||
}
|
||||
|
||||
func (l *LimitRanger) ValidateInitialization() error {
|
||||
if l.lister == nil {
|
||||
return fmt.Errorf("missing limitRange lister")
|
||||
}
|
||||
if l.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
|
||||
func (l *LimitRanger) Admit(a admission.Attributes) (err error) {
|
||||
return l.runLimitFunc(a, l.actions.MutateLimit)
|
||||
}
|
||||
|
||||
// Validate admits resources into cluster that do not violate any defined LimitRange in the namespace
|
||||
func (l *LimitRanger) Validate(a admission.Attributes) (err error) {
|
||||
return l.runLimitFunc(a, l.actions.ValidateLimit)
|
||||
}
|
||||
|
||||
func (l *LimitRanger) runLimitFunc(a admission.Attributes, limitFn func(limitRange *api.LimitRange, kind string, obj runtime.Object) error) (err error) {
|
||||
if !l.actions.SupportsAttributes(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
obj := a.GetObject()
|
||||
name := "Unknown"
|
||||
if obj != nil {
|
||||
name, _ = meta.NewAccessor().Name(obj)
|
||||
if len(name) == 0 {
|
||||
name, _ = meta.NewAccessor().GenerateName(obj)
|
||||
}
|
||||
}
|
||||
|
||||
items, err := l.GetLimitRanges(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure it meets each prescribed min/max
|
||||
for i := range items {
|
||||
limitRange := items[i]
|
||||
|
||||
if !l.actions.SupportsLimit(limitRange) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = limitFn(limitRange, a.GetResource().Resource, a.GetObject())
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LimitRanger) GetLimitRanges(a admission.Attributes) ([]*api.LimitRange, error) {
|
||||
items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource()))
|
||||
}
|
||||
|
||||
// if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it.
|
||||
if len(items) == 0 {
|
||||
lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace())
|
||||
if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
|
||||
// TODO: If there are multiple operations at the same time and cache has just expired,
|
||||
// this may cause multiple List operations being issued at the same time.
|
||||
// If there is already in-flight List() for a given namespace, we should wait until
|
||||
// it is finished and cache is updated instead of doing the same, also to avoid
|
||||
// throttling - see #22422 for details.
|
||||
liveList, err := l.client.Core().LimitRanges(a.GetNamespace()).List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, admission.NewForbidden(a, err)
|
||||
}
|
||||
newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)}
|
||||
for i := range liveList.Items {
|
||||
newEntry.items = append(newEntry.items, &liveList.Items[i])
|
||||
}
|
||||
l.liveLookupCache.Add(a.GetNamespace(), newEntry)
|
||||
lruItemObj = newEntry
|
||||
}
|
||||
lruEntry := lruItemObj.(liveLookupEntry)
|
||||
|
||||
for i := range lruEntry.items {
|
||||
items = append(items, lruEntry.items[i])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// NewLimitRanger returns an object that enforces limits based on the supplied limit function
|
||||
func NewLimitRanger(actions LimitRangerActions) (*LimitRanger, error) {
|
||||
liveLookupCache, err := lru.New(10000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if actions == nil {
|
||||
actions = &DefaultLimitRangerActions{}
|
||||
}
|
||||
|
||||
return &LimitRanger{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
actions: actions,
|
||||
liveLookupCache: liveLookupCache,
|
||||
liveTTL: time.Duration(30 * time.Second),
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&LimitRanger{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&LimitRanger{})
|
||||
|
||||
func (a *LimitRanger) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
// defaultContainerResourceRequirements returns the default requirements for a container
|
||||
// the requirement.Limits are taken from the LimitRange defaults (if specified)
|
||||
// the requirement.Requests are taken from the LimitRange default request (if specified)
|
||||
func defaultContainerResourceRequirements(limitRange *api.LimitRange) api.ResourceRequirements {
|
||||
requirements := api.ResourceRequirements{}
|
||||
requirements.Requests = api.ResourceList{}
|
||||
requirements.Limits = api.ResourceList{}
|
||||
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
if limit.Type == api.LimitTypeContainer {
|
||||
for k, v := range limit.DefaultRequest {
|
||||
value := v.Copy()
|
||||
requirements.Requests[k] = *value
|
||||
}
|
||||
for k, v := range limit.Default {
|
||||
value := v.Copy()
|
||||
requirements.Limits[k] = *value
|
||||
}
|
||||
}
|
||||
}
|
||||
return requirements
|
||||
}
|
||||
|
||||
// mergeContainerResources handles defaulting all of the resources on a container.
|
||||
func mergeContainerResources(container *api.Container, defaultRequirements *api.ResourceRequirements, annotationPrefix string, annotations []string) []string {
|
||||
setRequests := []string{}
|
||||
setLimits := []string{}
|
||||
if container.Resources.Limits == nil {
|
||||
container.Resources.Limits = api.ResourceList{}
|
||||
}
|
||||
if container.Resources.Requests == nil {
|
||||
container.Resources.Requests = api.ResourceList{}
|
||||
}
|
||||
for k, v := range defaultRequirements.Limits {
|
||||
_, found := container.Resources.Limits[k]
|
||||
if !found {
|
||||
container.Resources.Limits[k] = *v.Copy()
|
||||
setLimits = append(setLimits, string(k))
|
||||
}
|
||||
}
|
||||
for k, v := range defaultRequirements.Requests {
|
||||
_, found := container.Resources.Requests[k]
|
||||
if !found {
|
||||
container.Resources.Requests[k] = *v.Copy()
|
||||
setRequests = append(setRequests, string(k))
|
||||
}
|
||||
}
|
||||
if len(setRequests) > 0 {
|
||||
sort.Strings(setRequests)
|
||||
a := strings.Join(setRequests, ", ") + fmt.Sprintf(" request for %s %s", annotationPrefix, container.Name)
|
||||
annotations = append(annotations, a)
|
||||
}
|
||||
if len(setLimits) > 0 {
|
||||
sort.Strings(setLimits)
|
||||
a := strings.Join(setLimits, ", ") + fmt.Sprintf(" limit for %s %s", annotationPrefix, container.Name)
|
||||
annotations = append(annotations, a)
|
||||
}
|
||||
return annotations
|
||||
}
|
||||
|
||||
// mergePodResourceRequirements merges enumerated requirements with default requirements
|
||||
// it annotates the pod with information about what requirements were modified
|
||||
func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) {
|
||||
annotations := []string{}
|
||||
|
||||
for i := range pod.Spec.Containers {
|
||||
annotations = mergeContainerResources(&pod.Spec.Containers[i], defaultRequirements, "container", annotations)
|
||||
}
|
||||
|
||||
for i := range pod.Spec.InitContainers {
|
||||
annotations = mergeContainerResources(&pod.Spec.InitContainers[i], defaultRequirements, "init container", annotations)
|
||||
}
|
||||
|
||||
if len(annotations) > 0 {
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
val := "LimitRanger plugin set: " + strings.Join(annotations, "; ")
|
||||
pod.ObjectMeta.Annotations[limitRangerAnnotation] = val
|
||||
}
|
||||
}
|
||||
|
||||
// requestLimitEnforcedValues returns the specified values at a common precision to support comparability
|
||||
func requestLimitEnforcedValues(requestQuantity, limitQuantity, enforcedQuantity resource.Quantity) (request, limit, enforced int64) {
|
||||
request = requestQuantity.Value()
|
||||
limit = limitQuantity.Value()
|
||||
enforced = enforcedQuantity.Value()
|
||||
// do a more precise comparison if possible (if the value won't overflow)
|
||||
if request <= resource.MaxMilliValue && limit <= resource.MaxMilliValue && enforced <= resource.MaxMilliValue {
|
||||
request = requestQuantity.MilliValue()
|
||||
limit = limitQuantity.MilliValue()
|
||||
enforced = enforcedQuantity.MilliValue()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// minConstraint enforces the min constraint over the specified resource
|
||||
func minConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
lim, limExists := limit[resourceName]
|
||||
observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
|
||||
|
||||
if !reqExists {
|
||||
return fmt.Errorf("minimum %s usage per %s is %s. No request is specified.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if observedReqValue < enforcedValue {
|
||||
return fmt.Errorf("minimum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
|
||||
}
|
||||
if limExists && (observedLimValue < enforcedValue) {
|
||||
return fmt.Errorf("minimum %s usage per %s is %s, but limit is %s.", resourceName, limitType, enforced.String(), lim.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxRequestConstraint enforces the max constraint over the specified resource
|
||||
// use when specify LimitType resource doesn't recognize limit values
|
||||
func maxRequestConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
observedReqValue, _, enforcedValue := requestLimitEnforcedValues(req, resource.Quantity{}, enforced)
|
||||
|
||||
if !reqExists {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s. No request is specified.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if observedReqValue > enforcedValue {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// maxConstraint enforces the max constraint over the specified resource
|
||||
func maxConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
lim, limExists := limit[resourceName]
|
||||
observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
|
||||
|
||||
if !limExists {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s. No limit is specified.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if observedLimValue > enforcedValue {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s, but limit is %s.", resourceName, limitType, enforced.String(), lim.String())
|
||||
}
|
||||
if reqExists && (observedReqValue > enforcedValue) {
|
||||
return fmt.Errorf("maximum %s usage per %s is %s, but request is %s.", resourceName, limitType, enforced.String(), req.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// limitRequestRatioConstraint enforces the limit to request ratio over the specified resource
|
||||
func limitRequestRatioConstraint(limitType api.LimitType, resourceName api.ResourceName, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
|
||||
req, reqExists := request[resourceName]
|
||||
lim, limExists := limit[resourceName]
|
||||
observedReqValue, observedLimValue, _ := requestLimitEnforcedValues(req, lim, enforced)
|
||||
|
||||
if !reqExists || (observedReqValue == int64(0)) {
|
||||
return fmt.Errorf("%s max limit to request ratio per %s is %s, but no request is specified or request is 0.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
if !limExists || (observedLimValue == int64(0)) {
|
||||
return fmt.Errorf("%s max limit to request ratio per %s is %s, but no limit is specified or limit is 0.", resourceName, limitType, enforced.String())
|
||||
}
|
||||
|
||||
observedRatio := float64(observedLimValue) / float64(observedReqValue)
|
||||
displayObservedRatio := observedRatio
|
||||
maxLimitRequestRatio := float64(enforced.Value())
|
||||
if enforced.Value() <= resource.MaxMilliValue {
|
||||
observedRatio = observedRatio * 1000
|
||||
maxLimitRequestRatio = float64(enforced.MilliValue())
|
||||
}
|
||||
|
||||
if observedRatio > maxLimitRequestRatio {
|
||||
return fmt.Errorf("%s max limit to request ratio per %s is %s, but provided ratio is %f.", resourceName, limitType, enforced.String(), displayObservedRatio)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sum takes the total of each named resource across all inputs
|
||||
// if a key is not in each input, then the output resource list will omit the key
|
||||
func sum(inputs []api.ResourceList) api.ResourceList {
|
||||
result := api.ResourceList{}
|
||||
keys := []api.ResourceName{}
|
||||
for i := range inputs {
|
||||
for k := range inputs[i] {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
for _, key := range keys {
|
||||
total, isSet := int64(0), true
|
||||
|
||||
for i := range inputs {
|
||||
input := inputs[i]
|
||||
v, exists := input[key]
|
||||
if exists {
|
||||
if key == api.ResourceCPU {
|
||||
total = total + v.MilliValue()
|
||||
} else {
|
||||
total = total + v.Value()
|
||||
}
|
||||
} else {
|
||||
isSet = false
|
||||
}
|
||||
}
|
||||
|
||||
if isSet {
|
||||
if key == api.ResourceCPU {
|
||||
result[key] = *(resource.NewMilliQuantity(total, resource.DecimalSI))
|
||||
} else {
|
||||
result[key] = *(resource.NewQuantity(total, resource.DecimalSI))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DefaultLimitRangerActions is the default implementation of LimitRangerActions.
|
||||
type DefaultLimitRangerActions struct{}
|
||||
|
||||
// ensure DefaultLimitRangerActions implements the LimitRangerActions interface.
|
||||
var _ LimitRangerActions = &DefaultLimitRangerActions{}
|
||||
|
||||
// Limit enforces resource requirements of incoming resources against enumerated constraints
|
||||
// on the LimitRange. It may modify the incoming object to apply default resource requirements
|
||||
// if not specified, and enumerated on the LimitRange
|
||||
func (d *DefaultLimitRangerActions) MutateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
|
||||
switch resourceName {
|
||||
case "pods":
|
||||
return PodMutateLimitFunc(limitRange, obj.(*api.Pod))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Limit enforces resource requirements of incoming resources against enumerated constraints
|
||||
// on the LimitRange. It may modify the incoming object to apply default resource requirements
|
||||
// if not specified, and enumerated on the LimitRange
|
||||
func (d *DefaultLimitRangerActions) ValidateLimit(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
|
||||
switch resourceName {
|
||||
case "pods":
|
||||
return PodValidateLimitFunc(limitRange, obj.(*api.Pod))
|
||||
case "persistentvolumeclaims":
|
||||
return PersistentVolumeClaimValidateLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs).
|
||||
// Also ignores any call that has a subresource defined.
|
||||
func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool {
|
||||
if a.GetSubresource() != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim")
|
||||
}
|
||||
|
||||
// SupportsLimit always returns true.
|
||||
func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *api.LimitRange) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimValidateLimitFunc enforces storage limits for PVCs.
|
||||
// Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange.
|
||||
// Claims will not be modified with default values because storage is a required part of pvc.Spec.
|
||||
// All storage enforced values *only* apply to pvc.Spec.Resources.Requests.
|
||||
func PersistentVolumeClaimValidateLimitFunc(limitRange *api.LimitRange, pvc *api.PersistentVolumeClaim) error {
|
||||
var errs []error
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
limitType := limit.Type
|
||||
if limitType == api.LimitTypePersistentVolumeClaim {
|
||||
for k, v := range limit.Min {
|
||||
// normal usage of minConstraint. pvc.Spec.Resources.Limits is not recognized as user input
|
||||
if err := minConstraint(limitType, k, v, pvc.Spec.Resources.Requests, api.ResourceList{}); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
// We want to enforce the max of the LimitRange against what
|
||||
// the user requested.
|
||||
if err := maxRequestConstraint(limitType, k, v, pvc.Spec.Resources.Requests); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// PodMutateLimitFunc sets resource requirements enumerated by the pod against
|
||||
// the specified LimitRange. The pod may be modified to apply default resource
|
||||
// requirements if not specified, and enumerated on the LimitRange
|
||||
func PodMutateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
|
||||
defaultResources := defaultContainerResourceRequirements(limitRange)
|
||||
mergePodResourceRequirements(pod, &defaultResources)
|
||||
return nil
|
||||
}
|
||||
|
||||
// PodValidateLimitFunc enforces resource requirements enumerated by the pod against
|
||||
// the specified LimitRange.
|
||||
func PodValidateLimitFunc(limitRange *api.LimitRange, pod *api.Pod) error {
|
||||
var errs []error
|
||||
|
||||
for i := range limitRange.Spec.Limits {
|
||||
limit := limitRange.Spec.Limits[i]
|
||||
limitType := limit.Type
|
||||
// enforce container limits
|
||||
if limitType == api.LimitTypeContainer {
|
||||
for j := range pod.Spec.Containers {
|
||||
container := &pod.Spec.Containers[j]
|
||||
for k, v := range limit.Min {
|
||||
if err := minConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
if err := maxConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.MaxLimitRequestRatio {
|
||||
if err := limitRequestRatioConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for j := range pod.Spec.InitContainers {
|
||||
container := &pod.Spec.InitContainers[j]
|
||||
for k, v := range limit.Min {
|
||||
if err := minConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
if err := maxConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.MaxLimitRequestRatio {
|
||||
if err := limitRequestRatioConstraint(limitType, k, v, container.Resources.Requests, container.Resources.Limits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// enforce pod limits on init containers
|
||||
if limitType == api.LimitTypePod {
|
||||
containerRequests, containerLimits := []api.ResourceList{}, []api.ResourceList{}
|
||||
for j := range pod.Spec.Containers {
|
||||
container := &pod.Spec.Containers[j]
|
||||
containerRequests = append(containerRequests, container.Resources.Requests)
|
||||
containerLimits = append(containerLimits, container.Resources.Limits)
|
||||
}
|
||||
podRequests := sum(containerRequests)
|
||||
podLimits := sum(containerLimits)
|
||||
for j := range pod.Spec.InitContainers {
|
||||
container := &pod.Spec.InitContainers[j]
|
||||
// take max(sum_containers, any_init_container)
|
||||
for k, v := range container.Resources.Requests {
|
||||
if v2, ok := podRequests[k]; ok {
|
||||
if v.Cmp(v2) > 0 {
|
||||
podRequests[k] = v
|
||||
}
|
||||
} else {
|
||||
podRequests[k] = v
|
||||
}
|
||||
}
|
||||
for k, v := range container.Resources.Limits {
|
||||
if v2, ok := podLimits[k]; ok {
|
||||
if v.Cmp(v2) > 0 {
|
||||
podLimits[k] = v
|
||||
}
|
||||
} else {
|
||||
podLimits[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Min {
|
||||
if err := minConstraint(limitType, k, v, podRequests, podLimits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.Max {
|
||||
if err := maxConstraint(limitType, k, v, podRequests, podLimits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
for k, v := range limit.MaxLimitRequestRatio {
|
||||
if err := limitRequestRatioConstraint(limitType, k, v, podRequests, podLimits); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
828
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go
generated
vendored
Normal file
828
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,828 @@
|
||||
/*
|
||||
Copyright 2014 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 limitranger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
func getComputeResourceList(cpu, memory string) api.ResourceList {
|
||||
res := api.ResourceList{}
|
||||
if cpu != "" {
|
||||
res[api.ResourceCPU] = resource.MustParse(cpu)
|
||||
}
|
||||
if memory != "" {
|
||||
res[api.ResourceMemory] = resource.MustParse(memory)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getStorageResourceList(storage string) api.ResourceList {
|
||||
res := api.ResourceList{}
|
||||
if storage != "" {
|
||||
res[api.ResourceStorage] = resource.MustParse(storage)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func getResourceRequirements(requests, limits api.ResourceList) api.ResourceRequirements {
|
||||
res := api.ResourceRequirements{}
|
||||
res.Requests = requests
|
||||
res.Limits = limits
|
||||
return res
|
||||
}
|
||||
|
||||
// createLimitRange creates a limit range with the specified data
|
||||
func createLimitRange(limitType api.LimitType, min, max, defaultLimit, defaultRequest, maxLimitRequestRatio api.ResourceList) api.LimitRange {
|
||||
return api.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: limitType,
|
||||
Min: min,
|
||||
Max: max,
|
||||
Default: defaultLimit,
|
||||
DefaultRequest: defaultRequest,
|
||||
MaxLimitRequestRatio: maxLimitRequestRatio,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validLimitRange() api.LimitRange {
|
||||
return api.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: getComputeResourceList("200m", "4Gi"),
|
||||
Min: getComputeResourceList("50m", "2Mi"),
|
||||
},
|
||||
{
|
||||
Type: api.LimitTypeContainer,
|
||||
Max: getComputeResourceList("100m", "2Gi"),
|
||||
Min: getComputeResourceList("25m", "1Mi"),
|
||||
Default: getComputeResourceList("75m", "10Mi"),
|
||||
DefaultRequest: getComputeResourceList("50m", "5Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validLimitRangeNoDefaults() api.LimitRange {
|
||||
return api.LimitRange{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "abc",
|
||||
Namespace: "test",
|
||||
},
|
||||
Spec: api.LimitRangeSpec{
|
||||
Limits: []api.LimitRangeItem{
|
||||
{
|
||||
Type: api.LimitTypePod,
|
||||
Max: getComputeResourceList("200m", "4Gi"),
|
||||
Min: getComputeResourceList("50m", "2Mi"),
|
||||
},
|
||||
{
|
||||
Type: api.LimitTypeContainer,
|
||||
Max: getComputeResourceList("100m", "2Gi"),
|
||||
Min: getComputeResourceList("25m", "1Mi"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func validPod(name string, numContainers int, resources api.ResourceRequirements) api.Pod {
|
||||
pod := api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PodSpec{},
|
||||
}
|
||||
pod.Spec.Containers = make([]api.Container, 0, numContainers)
|
||||
for i := 0; i < numContainers; i++ {
|
||||
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{
|
||||
Image: "foo:V" + strconv.Itoa(i),
|
||||
Resources: resources,
|
||||
Name: "foo-" + strconv.Itoa(i),
|
||||
})
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func validPodInit(pod api.Pod, resources ...api.ResourceRequirements) api.Pod {
|
||||
for i := 0; i < len(resources); i++ {
|
||||
pod.Spec.InitContainers = append(pod.Spec.InitContainers, api.Container{
|
||||
Image: "foo:V" + strconv.Itoa(i),
|
||||
Resources: resources[i],
|
||||
Name: "foo-" + strconv.Itoa(i),
|
||||
})
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func TestDefaultContainerResourceRequirements(t *testing.T) {
|
||||
limitRange := validLimitRange()
|
||||
expected := api.ResourceRequirements{
|
||||
Requests: getComputeResourceList("50m", "5Mi"),
|
||||
Limits: getComputeResourceList("75m", "10Mi"),
|
||||
}
|
||||
|
||||
actual := defaultContainerResourceRequirements(&limitRange)
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("actual.Limits != expected.Limits; %v != %v", actual.Limits, expected.Limits)
|
||||
t.Errorf("actual.Requests != expected.Requests; %v != %v", actual.Requests, expected.Requests)
|
||||
t.Errorf("expected != actual; %v != %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyAnnotation(t *testing.T, pod *api.Pod, expected string) {
|
||||
a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]
|
||||
if !ok {
|
||||
t.Errorf("No annotation but expected %v", expected)
|
||||
}
|
||||
if a != expected {
|
||||
t.Errorf("Wrong annotation set by Limit Ranger: got %v, expected %v", a, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func expectNoAnnotation(t *testing.T, pod *api.Pod) {
|
||||
if a, ok := pod.ObjectMeta.Annotations[limitRangerAnnotation]; ok {
|
||||
t.Errorf("Expected no annotation but got %v", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergePodResourceRequirements(t *testing.T) {
|
||||
limitRange := validLimitRange()
|
||||
|
||||
// pod with no resources enumerated should get each resource from default request
|
||||
expected := getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))
|
||||
pod := validPod("empty-resources", 1, expected)
|
||||
defaultRequirements := defaultContainerResourceRequirements(&limitRange)
|
||||
mergePodResourceRequirements(&pod, &defaultRequirements)
|
||||
for i := range pod.Spec.Containers {
|
||||
actual := pod.Spec.Containers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu, memory request for container foo-0; cpu, memory limit for container foo-0")
|
||||
|
||||
// pod with some resources enumerated should only merge empty
|
||||
input := getResourceRequirements(getComputeResourceList("", "512Mi"), getComputeResourceList("", ""))
|
||||
pod = validPodInit(validPod("limit-memory", 1, input), input)
|
||||
expected = api.ResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceCPU: defaultRequirements.Requests[api.ResourceCPU],
|
||||
api.ResourceMemory: resource.MustParse("512Mi"),
|
||||
},
|
||||
Limits: defaultRequirements.Limits,
|
||||
}
|
||||
mergePodResourceRequirements(&pod, &defaultRequirements)
|
||||
for i := range pod.Spec.Containers {
|
||||
actual := pod.Spec.Containers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.InitContainers {
|
||||
actual := pod.Spec.InitContainers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
verifyAnnotation(t, &pod, "LimitRanger plugin set: cpu request for container foo-0; cpu, memory limit for container foo-0")
|
||||
|
||||
// pod with all resources enumerated should not merge anything
|
||||
input = getResourceRequirements(getComputeResourceList("100m", "512Mi"), getComputeResourceList("200m", "1G"))
|
||||
initInputs := []api.ResourceRequirements{getResourceRequirements(getComputeResourceList("200m", "1G"), getComputeResourceList("400m", "2G"))}
|
||||
pod = validPodInit(validPod("limit-memory", 1, input), initInputs...)
|
||||
expected = input
|
||||
mergePodResourceRequirements(&pod, &defaultRequirements)
|
||||
for i := range pod.Spec.Containers {
|
||||
actual := pod.Spec.Containers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(expected, actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, expected, actual)
|
||||
}
|
||||
}
|
||||
for i := range pod.Spec.InitContainers {
|
||||
actual := pod.Spec.InitContainers[i].Resources
|
||||
if !apiequality.Semantic.DeepEqual(initInputs[i], actual) {
|
||||
t.Errorf("pod %v, expected != actual; %v != %v", pod.Name, initInputs[i], actual)
|
||||
}
|
||||
}
|
||||
expectNoAnnotation(t, &pod)
|
||||
}
|
||||
|
||||
func TestPodLimitFunc(t *testing.T) {
|
||||
type testCase struct {
|
||||
pod api.Pod
|
||||
limitRange api.LimitRange
|
||||
}
|
||||
|
||||
successCases := []testCase{
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("100m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("750m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1.5", "")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-memory-request", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
getResourceRequirements(getComputeResourceList("", "100Mi"), getComputeResourceList("", "")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-memory-request-limit", 2, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
getResourceRequirements(getComputeResourceList("", "80Mi"), getComputeResourceList("", "100Mi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-cpu-request-limit", 2, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("2", "")),
|
||||
getResourceRequirements(getComputeResourceList("1", ""), getComputeResourceList("1", "")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-cpu-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
|
||||
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2", "")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-request-limit", 2, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-limit", 2, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "300Mi"), getComputeResourceList("", "450Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("600Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("600Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
getResourceRequirements(getLocalStorageResourceList("100Mi"), getLocalStorageResourceList("")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
getResourceRequirements(getLocalStorageResourceList("80Mi"), getLocalStorageResourceList("100Mi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-ratio", 3, getResourceRequirements(getLocalStorageResourceList("300Mi"), getLocalStorageResourceList("450Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("2Gi"), api.ResourceList{}, api.ResourceList{}, getLocalStorageResourceList("1.5")),
|
||||
},
|
||||
}
|
||||
for i := range successCases {
|
||||
test := successCases[i]
|
||||
err := PodMutateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
|
||||
}
|
||||
err = PodValidateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []testCase{
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("40m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("50m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "40Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-min-memory-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getComputeResourceList("", "50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("2500m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("2500m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-cpu-ratio", 1, getResourceRequirements(getComputeResourceList("1250m", ""), getComputeResourceList("2500m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, getComputeResourceList("1", "")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-request-limit", 1, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-max-mem-no-request-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-cpu-request-limit", 1, getResourceRequirements(getComputeResourceList("75m", ""), getComputeResourceList("200m", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("100m", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-memory-request-limit", 1, getResourceRequirements(getComputeResourceList("", "60Mi"), getComputeResourceList("", "100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getComputeResourceList("", "100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-request-limit", 3, getResourceRequirements(getComputeResourceList("500m", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-cpu-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("1", ""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("2", ""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-request-limit", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-limit", 3, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-mem-limit", 1, getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "500Mi"))),
|
||||
getResourceRequirements(getComputeResourceList("", ""), getComputeResourceList("", "1.5Gi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-mem-ratio", 3, getResourceRequirements(getComputeResourceList("", "250Mi"), getComputeResourceList("", "500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getComputeResourceList("", "2Gi"), api.ResourceList{}, api.ResourceList{}, getComputeResourceList("", "1.5")),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-min-local-ephemeral-storage-no-request-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-1-max-local-ephemeral-storage-no-request-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request", 2, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("40Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-min-local-ephemeral-storage-no-request-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, getLocalStorageResourceList("50Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-request-limit", 2, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("2Gi"))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("ctr-2-max-local-ephemeral-storage-no-request-limit", 2, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypeContainer, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-min-local-ephemeral-storage-request-limit", 1, getResourceRequirements(getLocalStorageResourceList("60Mi"), getLocalStorageResourceList("100Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, getLocalStorageResourceList("100Mi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-request-limit", 3, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-limit", 3, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPodInit(
|
||||
validPod("pod-init-max-local-ephemeral-storage-limit", 1, getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("500Mi"))),
|
||||
getResourceRequirements(getLocalStorageResourceList(""), getLocalStorageResourceList("1.5Gi")),
|
||||
),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pod: validPod("pod-max-local-ephemeral-storage-ratio", 3, getResourceRequirements(getLocalStorageResourceList("250Mi"), getLocalStorageResourceList("500Mi"))),
|
||||
limitRange: createLimitRange(api.LimitTypePod, api.ResourceList{}, getLocalStorageResourceList("2Gi"), api.ResourceList{}, api.ResourceList{}, getLocalStorageResourceList("1.5")),
|
||||
},
|
||||
}
|
||||
for i := range errorCases {
|
||||
test := errorCases[i]
|
||||
err := PodMutateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pod: %s, %v", test.pod.Name, err)
|
||||
}
|
||||
err = PodValidateLimitFunc(&test.limitRange, &test.pod)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for pod: %s", test.pod.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalStorageResourceList(ephemeralStorage string) api.ResourceList {
|
||||
res := api.ResourceList{}
|
||||
if ephemeralStorage != "" {
|
||||
res[api.ResourceEphemeralStorage] = resource.MustParse(ephemeralStorage)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func TestPodLimitFuncApplyDefault(t *testing.T) {
|
||||
limitRange := validLimitRange()
|
||||
testPod := validPodInit(validPod("foo", 1, getResourceRequirements(api.ResourceList{}, api.ResourceList{})), getResourceRequirements(api.ResourceList{}, api.ResourceList{}))
|
||||
err := PodMutateLimitFunc(&limitRange, &testPod)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for valid pod: %s, %v", testPod.Name, err)
|
||||
}
|
||||
|
||||
for i := range testPod.Spec.Containers {
|
||||
container := testPod.Spec.Containers[i]
|
||||
limitMemory := container.Resources.Limits.Memory().String()
|
||||
limitCpu := container.Resources.Limits.Cpu().String()
|
||||
requestMemory := container.Resources.Requests.Memory().String()
|
||||
requestCpu := container.Resources.Requests.Cpu().String()
|
||||
|
||||
if limitMemory != "10Mi" {
|
||||
t.Errorf("Unexpected limit memory value %s", limitMemory)
|
||||
}
|
||||
if limitCpu != "75m" {
|
||||
t.Errorf("Unexpected limit cpu value %s", limitCpu)
|
||||
}
|
||||
if requestMemory != "5Mi" {
|
||||
t.Errorf("Unexpected request memory value %s", requestMemory)
|
||||
}
|
||||
if requestCpu != "50m" {
|
||||
t.Errorf("Unexpected request cpu value %s", requestCpu)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range testPod.Spec.InitContainers {
|
||||
container := testPod.Spec.InitContainers[i]
|
||||
limitMemory := container.Resources.Limits.Memory().String()
|
||||
limitCpu := container.Resources.Limits.Cpu().String()
|
||||
requestMemory := container.Resources.Requests.Memory().String()
|
||||
requestCpu := container.Resources.Requests.Cpu().String()
|
||||
|
||||
if limitMemory != "10Mi" {
|
||||
t.Errorf("Unexpected limit memory value %s", limitMemory)
|
||||
}
|
||||
if limitCpu != "75m" {
|
||||
t.Errorf("Unexpected limit cpu value %s", limitCpu)
|
||||
}
|
||||
if requestMemory != "5Mi" {
|
||||
t.Errorf("Unexpected request memory value %s", requestMemory)
|
||||
}
|
||||
if requestCpu != "50m" {
|
||||
t.Errorf("Unexpected request cpu value %s", requestCpu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLimitRangerIgnoresSubresource(t *testing.T) {
|
||||
limitRange := validLimitRangeNoDefaults()
|
||||
mockClient := newMockClientForTest([]api.LimitRange{limitRange})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
testPod := validPod("testPod", 1, api.ResourceRequirements{})
|
||||
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
|
||||
}
|
||||
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Should have ignored calls to any subresource of pod %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestLimitRangerAdmitPod(t *testing.T) {
|
||||
limitRange := validLimitRangeNoDefaults()
|
||||
mockClient := newMockClientForTest([]api.LimitRange{limitRange})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
testPod := validPod("testPod", 1, api.ResourceRequirements{})
|
||||
err = handler.Admit(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error since the pod did not specify resource limits in its update call")
|
||||
}
|
||||
|
||||
err = handler.Validate(admission.NewAttributesRecord(&testPod, nil, api.Kind("Pod").WithVersion("version"), limitRange.Namespace, "testPod", api.Resource("pods").WithVersion("version"), "status", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Should have ignored calls to any subresource of pod %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of limit ranges
|
||||
func newMockClientForTest(limitRanges []api.LimitRange) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "limitranges", func(action core.Action) (bool, runtime.Object, error) {
|
||||
limitRangeList := &api.LimitRangeList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(limitRanges)),
|
||||
},
|
||||
}
|
||||
for index, value := range limitRanges {
|
||||
value.ResourceVersion = fmt.Sprintf("%d", index)
|
||||
limitRangeList.Items = append(limitRangeList.Items, value)
|
||||
}
|
||||
return true, limitRangeList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newHandlerForTest returns a handler configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (*LimitRanger, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler, err := NewLimitRanger(&DefaultLimitRangerActions{})
|
||||
if err != nil {
|
||||
return nil, f, err
|
||||
}
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err = admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
func validPersistentVolumeClaim(name string, resources api.ResourceRequirements) api.PersistentVolumeClaim {
|
||||
pvc := api.PersistentVolumeClaim{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test"},
|
||||
Spec: api.PersistentVolumeClaimSpec{
|
||||
Resources: resources,
|
||||
},
|
||||
}
|
||||
return pvc
|
||||
}
|
||||
|
||||
func TestPersistentVolumeClaimLimitFunc(t *testing.T) {
|
||||
type testCase struct {
|
||||
pvc api.PersistentVolumeClaim
|
||||
limitRange api.LimitRange
|
||||
}
|
||||
|
||||
successCases := []testCase{
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-is-min-storage-request", getResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-is-max-storage-request", getResourceRequirements(getStorageResourceList("1Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, api.ResourceList{}, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-no-minmax-storage-request", getResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList(""), getStorageResourceList(""), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-within-minmax-storage-request", getResourceRequirements(getStorageResourceList("5Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("10Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
}
|
||||
for i := range successCases {
|
||||
test := successCases[i]
|
||||
err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for pvc: %s, %v", test.pvc.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []testCase{
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-below-min-storage-request", getResourceRequirements(getStorageResourceList("500Mi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
{
|
||||
pvc: validPersistentVolumeClaim("pvc-exceeds-max-storage-request", getResourceRequirements(getStorageResourceList("100Gi"), getStorageResourceList(""))),
|
||||
limitRange: createLimitRange(api.LimitTypePersistentVolumeClaim, getStorageResourceList("1Gi"), getStorageResourceList("1Gi"), api.ResourceList{}, api.ResourceList{}, api.ResourceList{}),
|
||||
},
|
||||
}
|
||||
for i := range errorCases {
|
||||
test := errorCases[i]
|
||||
err := PersistentVolumeClaimValidateLimitFunc(&test.limitRange, &test.pvc)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for pvc: %s", test.pvc.Name)
|
||||
}
|
||||
}
|
||||
}
|
36
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/interfaces.go
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/plugin/pkg/admission/limitranger/interfaces.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2014 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 limitranger
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
)
|
||||
|
||||
type LimitRangerActions interface {
|
||||
// MutateLimit is a pluggable function to set limits on the object.
|
||||
MutateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error
|
||||
// ValidateLimits is a pluggable function to enforce limits on the object.
|
||||
ValidateLimit(limitRange *api.LimitRange, kind string, obj runtime.Object) error
|
||||
// SupportsAttributes is a pluggable function to allow overridding what resources the limitranger
|
||||
// supports.
|
||||
SupportsAttributes(attr admission.Attributes) bool
|
||||
// SupportsLimit is a pluggable function to allow ignoring limits that should not be applied
|
||||
// for any reason.
|
||||
SupportsLimit(limitRange *api.LimitRange) bool
|
||||
}
|
56
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/BUILD
generated
vendored
Normal file
56
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/BUILD
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
119
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission.go
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission.go
generated
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
Copyright 2014 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 autoprovision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("NamespaceAutoProvision", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewProvision(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Provision is an implementation of admission.Interface.
|
||||
// It looks at all incoming requests in a namespace context, and if the namespace does not exist, it creates one.
|
||||
// It is useful in deployments that do not want to restrict creation of a namespace prior to its usage.
|
||||
type Provision struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Provision{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Provision{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Provision{})
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (p *Provision) Admit(a admission.Attributes) error {
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
return nil
|
||||
}
|
||||
// we need to wait for our caches to warm
|
||||
if !p.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
_, err := p.namespaceLister.Get(a.GetNamespace())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !errors.IsNotFound(err) {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
namespace := &api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: a.GetNamespace(),
|
||||
Namespace: "",
|
||||
},
|
||||
Status: api.NamespaceStatus{},
|
||||
}
|
||||
|
||||
_, err = p.client.Core().Namespaces().Create(namespace)
|
||||
if err != nil && !errors.IsAlreadyExists(err) {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewProvision creates a new namespace provision admission control handler
|
||||
func NewProvision() *Provision {
|
||||
return &Provision{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
}
|
||||
}
|
||||
|
||||
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
|
||||
func (p *Provision) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
p.client = client
|
||||
}
|
||||
|
||||
// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface.
|
||||
func (p *Provision) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().InternalVersion().Namespaces()
|
||||
p.namespaceLister = namespaceInformer.Lister()
|
||||
p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (p *Provision) ValidateInitialization() error {
|
||||
if p.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if p.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
172
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go
generated
vendored
Normal file
172
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/autoprovision/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2014 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 autoprovision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.MutationInterface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewProvision()
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
|
||||
func newMockClientForTest(namespaces []string) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
for i, ns := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ns,
|
||||
ResourceVersion: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// hasCreateNamespaceAction returns true if it has the create namespace action
|
||||
func hasCreateNamespaceAction(mockClient *fake.Clientset) bool {
|
||||
for _, action := range mockClient.Actions() {
|
||||
if action.GetVerb() == "create" && action.GetResource().Resource == "namespaces" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TestAdmission verifies a namespace is created on create requests for namespace managed resources
|
||||
func TestAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if !hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("expected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceExists verifies that no client call is made when a namespace already exists
|
||||
func TestAdmissionNamespaceExists(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{namespace})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("unexpected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIgnoreAdmission validates that a request is ignored if its not a create
|
||||
func TestIgnoreAdmission(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
chainHandler := admission.NewChainHandler(handler)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = chainHandler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Update, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
if hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("unexpected create namespace action")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmissionWithLatentCache(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
mockClient.AddReactor("create", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.NewAlreadyExists(api.Resource("namespaces"), namespace)
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
|
||||
if !hasCreateNamespaceAction(mockClient) {
|
||||
t.Errorf("expected create namespace action")
|
||||
}
|
||||
}
|
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/BUILD
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/namespace/exists",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
114
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission.go
generated
vendored
Normal file
114
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission.go
generated
vendored
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
Copyright 2014 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 exists
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("NamespaceExists", func(config io.Reader) (admission.Interface, error) {
|
||||
return NewExists(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Exists is an implementation of admission.Interface.
|
||||
// It rejects all incoming requests in a namespace context if the namespace does not exist.
|
||||
// It is useful in deployments that want to enforce pre-declaration of a Namespace resource.
|
||||
type Exists struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
}
|
||||
|
||||
var _ admission.ValidationInterface = &Exists{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&Exists{})
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&Exists{})
|
||||
|
||||
// Validate makes an admission decision based on the request attributes
|
||||
func (e *Exists) Validate(a admission.Attributes) error {
|
||||
// if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do
|
||||
// if we're here, then the API server has found a route, which means that if we have a non-empty namespace
|
||||
// its a namespaced resource.
|
||||
if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !e.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
_, err := e.namespaceLister.Get(a.GetNamespace())
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !errors.IsNotFound(err) {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
|
||||
_, err = e.client.Core().Namespaces().Get(a.GetNamespace(), metav1.GetOptions{})
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewExists creates a new namespace exists admission control handler
|
||||
func NewExists() *Exists {
|
||||
return &Exists{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
}
|
||||
}
|
||||
|
||||
// SetInternalKubeClientSet implements the WantsInternalKubeClientSet interface.
|
||||
func (e *Exists) SetInternalKubeClientSet(client internalclientset.Interface) {
|
||||
e.client = client
|
||||
}
|
||||
|
||||
// SetInternalKubeInformerFactory implements the WantsInternalKubeInformerFactory interface.
|
||||
func (e *Exists) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().InternalVersion().Namespaces()
|
||||
e.namespaceLister = namespaceInformer.Lister()
|
||||
e.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (e *Exists) ValidateInitialization() error {
|
||||
if e.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if e.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
118
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission_test.go
generated
vendored
Normal file
118
vendor/k8s.io/kubernetes/plugin/pkg/admission/namespace/exists/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
/*
|
||||
Copyright 2014 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 exists
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
core "k8s.io/client-go/testing"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
kubeadmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
// newHandlerForTest returns the admission controller configured for testing.
|
||||
func newHandlerForTest(c clientset.Interface) (admission.ValidationInterface, informers.SharedInformerFactory, error) {
|
||||
f := informers.NewSharedInformerFactory(c, 5*time.Minute)
|
||||
handler := NewExists()
|
||||
pluginInitializer := kubeadmission.NewPluginInitializer(c, f, nil, nil, nil)
|
||||
pluginInitializer.Initialize(handler)
|
||||
err := admission.ValidateInitialization(handler)
|
||||
return handler, f, err
|
||||
}
|
||||
|
||||
// newMockClientForTest creates a mock client that returns a client configured for the specified list of namespaces.
|
||||
func newMockClientForTest(namespaces []string) *fake.Clientset {
|
||||
mockClient := &fake.Clientset{}
|
||||
mockClient.AddReactor("list", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
namespaceList := &api.NamespaceList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
ResourceVersion: fmt.Sprintf("%d", len(namespaces)),
|
||||
},
|
||||
}
|
||||
for i, ns := range namespaces {
|
||||
namespaceList.Items = append(namespaceList.Items, api.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ns,
|
||||
ResourceVersion: fmt.Sprintf("%d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
return true, namespaceList, nil
|
||||
})
|
||||
return mockClient
|
||||
}
|
||||
|
||||
// newPod returns a new pod for the specified namespace
|
||||
func newPod(namespace string) api.Pod {
|
||||
return api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: namespace},
|
||||
Spec: api.PodSpec{
|
||||
Volumes: []api.Volume{{Name: "vol"}},
|
||||
Containers: []api.Container{{Name: "ctr", Image: "image"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceExists verifies pod is admitted only if namespace exists.
|
||||
func TestAdmissionNamespaceExists(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{namespace})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
||||
|
||||
// TestAdmissionNamespaceDoesNotExist verifies pod is not admitted if namespace does not exist.
|
||||
func TestAdmissionNamespaceDoesNotExist(t *testing.T) {
|
||||
namespace := "test"
|
||||
mockClient := newMockClientForTest([]string{})
|
||||
mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, fmt.Errorf("nope, out of luck")
|
||||
})
|
||||
handler, informerFactory, err := newHandlerForTest(mockClient)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error initializing handler: %v", err)
|
||||
}
|
||||
informerFactory.Start(wait.NeverStop)
|
||||
|
||||
pod := newPod(namespace)
|
||||
err = handler.Validate(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
actions := ""
|
||||
for _, action := range mockClient.Actions() {
|
||||
actions = actions + action.GetVerb() + ":" + action.GetResource().Resource + ":" + action.GetSubresource() + ", "
|
||||
}
|
||||
t.Errorf("expected error returned from admission handler: %v", actions)
|
||||
}
|
||||
}
|
59
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/BUILD
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/BUILD
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
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"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/noderestriction",
|
||||
deps = [
|
||||
"//pkg/api/pod:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/policy:go_default_library",
|
||||
"//pkg/auth/nodeidentifier:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/noderestriction",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/policy:go_default_library",
|
||||
"//pkg/auth/nodeidentifier:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion: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/apiserver/pkg/authentication/user:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
8
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/OWNERS
generated
vendored
Normal file
8
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/OWNERS
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
approvers:
|
||||
- deads2k
|
||||
- liggitt
|
||||
- tallclair
|
||||
reviewers:
|
||||
- deads2k
|
||||
- liggitt
|
||||
- tallclair
|
342
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission.go
generated
vendored
Normal file
342
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission.go
generated
vendored
Normal file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
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 noderestriction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
podutil "k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginName = "NodeRestriction"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier()), nil
|
||||
})
|
||||
}
|
||||
|
||||
// NewPlugin creates a new NodeRestriction admission plugin.
|
||||
// This plugin identifies requests from nodes
|
||||
func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier) *nodePlugin {
|
||||
return &nodePlugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
nodeIdentifier: nodeIdentifier,
|
||||
}
|
||||
}
|
||||
|
||||
// nodePlugin holds state for and implements the admission plugin.
|
||||
type nodePlugin struct {
|
||||
*admission.Handler
|
||||
nodeIdentifier nodeidentifier.NodeIdentifier
|
||||
podsGetter coreinternalversion.PodsGetter
|
||||
}
|
||||
|
||||
var (
|
||||
_ = admission.Interface(&nodePlugin{})
|
||||
_ = kubeapiserveradmission.WantsInternalKubeClientSet(&nodePlugin{})
|
||||
)
|
||||
|
||||
func (p *nodePlugin) SetInternalKubeClientSet(f internalclientset.Interface) {
|
||||
p.podsGetter = f.Core()
|
||||
}
|
||||
|
||||
func (p *nodePlugin) ValidateInitialization() error {
|
||||
if p.nodeIdentifier == nil {
|
||||
return fmt.Errorf("%s requires a node identifier", PluginName)
|
||||
}
|
||||
if p.podsGetter == nil {
|
||||
return fmt.Errorf("%s requires a pod getter", PluginName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
podResource = api.Resource("pods")
|
||||
nodeResource = api.Resource("nodes")
|
||||
pvcResource = api.Resource("persistentvolumeclaims")
|
||||
)
|
||||
|
||||
func (c *nodePlugin) Admit(a admission.Attributes) error {
|
||||
nodeName, isNode := c.nodeIdentifier.NodeIdentity(a.GetUserInfo())
|
||||
|
||||
// Our job is just to restrict nodes
|
||||
if !isNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(nodeName) == 0 {
|
||||
// disallow requests we cannot match to a particular node
|
||||
return admission.NewForbidden(a, fmt.Errorf("could not determine node from user %q", a.GetUserInfo().GetName()))
|
||||
}
|
||||
|
||||
switch a.GetResource().GroupResource() {
|
||||
case podResource:
|
||||
switch a.GetSubresource() {
|
||||
case "":
|
||||
return c.admitPod(nodeName, a)
|
||||
case "status":
|
||||
return c.admitPodStatus(nodeName, a)
|
||||
case "eviction":
|
||||
return c.admitPodEviction(nodeName, a)
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q", a.GetSubresource()))
|
||||
}
|
||||
|
||||
case nodeResource:
|
||||
return c.admitNode(nodeName, a)
|
||||
|
||||
case pvcResource:
|
||||
switch a.GetSubresource() {
|
||||
case "status":
|
||||
return c.admitPVCStatus(nodeName, a)
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("may only update PVC status"))
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPod(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
// require a pod object
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// only allow nodes to create mirror pods
|
||||
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; !isMirrorPod {
|
||||
return admission.NewForbidden(a, fmt.Errorf("pod does not have %q annotation, node %q can only create mirror pods", api.MirrorPodAnnotationKey, nodeName))
|
||||
}
|
||||
|
||||
// only allow nodes to create a pod bound to itself
|
||||
if pod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
|
||||
// don't allow a node to create a pod that references any other API objects
|
||||
if pod.Spec.ServiceAccountName != "" {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference a service account", nodeName))
|
||||
}
|
||||
hasSecrets := false
|
||||
podutil.VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false })
|
||||
if hasSecrets {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference secrets", nodeName))
|
||||
}
|
||||
hasConfigMaps := false
|
||||
podutil.VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false })
|
||||
if hasConfigMaps {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference configmaps", nodeName))
|
||||
}
|
||||
for _, v := range pod.Spec.Volumes {
|
||||
if v.PersistentVolumeClaim != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference persistentvolumeclaims", nodeName))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
case admission.Delete:
|
||||
// get the existing pod from the server cache
|
||||
existingPod, err := c.podsGetter.Pods(a.GetNamespace()).Get(a.GetName(), v1.GetOptions{ResourceVersion: "0"})
|
||||
if errors.IsNotFound(err) {
|
||||
// wasn't found in the server cache, do a live lookup before forbidding
|
||||
existingPod, err = c.podsGetter.Pods(a.GetNamespace()).Get(a.GetName(), v1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
// only allow a node to delete a pod bound to itself
|
||||
if existingPod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can only delete pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPodStatus(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Update:
|
||||
// require an existing pod
|
||||
pod, ok := a.GetOldObject().(*api.Pod)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
|
||||
}
|
||||
// only allow a node to update status of a pod bound to itself
|
||||
if pod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q can only update pod status for pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPodEviction(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Create:
|
||||
// require eviction to an existing pod object
|
||||
eviction, ok := a.GetObject().(*policy.Eviction)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
// use pod name from the admission attributes, if set, rather than from the submitted Eviction object
|
||||
podName := a.GetName()
|
||||
if len(podName) == 0 {
|
||||
if len(eviction.Name) == 0 {
|
||||
return admission.NewForbidden(a, fmt.Errorf("could not determine pod from request data"))
|
||||
}
|
||||
podName = eviction.Name
|
||||
}
|
||||
// get the existing pod from the server cache
|
||||
existingPod, err := c.podsGetter.Pods(a.GetNamespace()).Get(podName, v1.GetOptions{ResourceVersion: "0"})
|
||||
if errors.IsNotFound(err) {
|
||||
// wasn't found in the server cache, do a live lookup before forbidding
|
||||
existingPod, err = c.podsGetter.Pods(a.GetNamespace()).Get(podName, v1.GetOptions{})
|
||||
if errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, err)
|
||||
}
|
||||
// only allow a node to evict a pod bound to itself
|
||||
if existingPod.Spec.NodeName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %s can only evict pods with spec.nodeName set to itself", nodeName))
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitPVCStatus(nodeName string, a admission.Attributes) error {
|
||||
switch a.GetOperation() {
|
||||
case admission.Update:
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q may not update persistentvolumeclaim metadata", nodeName))
|
||||
}
|
||||
|
||||
oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
|
||||
}
|
||||
|
||||
newPVC, ok := a.GetObject().(*api.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// make copies for comparison
|
||||
oldPVC = oldPVC.DeepCopy()
|
||||
newPVC = newPVC.DeepCopy()
|
||||
|
||||
// zero out resourceVersion to avoid comparing differences,
|
||||
// since the new object could leave it empty to indicate an unconditional update
|
||||
oldPVC.ObjectMeta.ResourceVersion = ""
|
||||
newPVC.ObjectMeta.ResourceVersion = ""
|
||||
|
||||
oldPVC.Status.Capacity = nil
|
||||
newPVC.Status.Capacity = nil
|
||||
|
||||
oldPVC.Status.Conditions = nil
|
||||
newPVC.Status.Conditions = nil
|
||||
|
||||
// ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
|
||||
if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q may not update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC)))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
default:
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nodePlugin) admitNode(nodeName string, a admission.Attributes) error {
|
||||
requestedName := a.GetName()
|
||||
if a.GetOperation() == admission.Create {
|
||||
node, ok := a.GetObject().(*api.Node)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// Don't allow a node to create its Node API object with the config source set.
|
||||
// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
|
||||
if node.Spec.ConfigSource != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot create with non-nil configSource"))
|
||||
}
|
||||
|
||||
// On create, get name from new object if unset in admission
|
||||
if len(requestedName) == 0 {
|
||||
requestedName = node.Name
|
||||
}
|
||||
}
|
||||
if requestedName != nodeName {
|
||||
return admission.NewForbidden(a, fmt.Errorf("node %q cannot modify node %q", nodeName, requestedName))
|
||||
}
|
||||
|
||||
if a.GetOperation() == admission.Update {
|
||||
node, ok := a.GetObject().(*api.Node)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
oldNode, ok := a.GetOldObject().(*api.Node)
|
||||
if !ok {
|
||||
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
|
||||
}
|
||||
|
||||
// Don't allow a node to update the config source on its Node API object.
|
||||
// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
|
||||
// We only do the check if the new node's configSource is non-nil; old kubelets might drop the field during a status update.
|
||||
if node.Spec.ConfigSource != nil && !apiequality.Semantic.DeepEqual(node.Spec.ConfigSource, oldNode.Spec.ConfigSource) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("cannot update configSource to a new non-nil configSource"))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
729
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission_test.go
generated
vendored
Normal file
729
vendor/k8s.io/kubernetes/plugin/pkg/admission/noderestriction/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,729 @@
|
||||
/*
|
||||
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 noderestriction
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
policyapi "k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
coreinternalversion "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
)
|
||||
|
||||
func makeTestPod(namespace, name, node string, mirror bool) *api.Pod {
|
||||
pod := &api.Pod{}
|
||||
pod.Namespace = namespace
|
||||
pod.Name = name
|
||||
pod.Spec.NodeName = node
|
||||
if mirror {
|
||||
pod.Annotations = map[string]string{api.MirrorPodAnnotationKey: "true"}
|
||||
}
|
||||
return pod
|
||||
}
|
||||
|
||||
func makeTestPodEviction(name string) *policy.Eviction {
|
||||
eviction := &policy.Eviction{}
|
||||
eviction.Name = name
|
||||
return eviction
|
||||
}
|
||||
|
||||
func Test_nodePlugin_Admit(t *testing.T) {
|
||||
var (
|
||||
mynode = &user.DefaultInfo{Name: "system:node:mynode", Groups: []string{"system:nodes"}}
|
||||
bob = &user.DefaultInfo{Name: "bob"}
|
||||
|
||||
mynodeObjMeta = metav1.ObjectMeta{Name: "mynode"}
|
||||
mynodeObj = &api.Node{ObjectMeta: mynodeObjMeta}
|
||||
mynodeObjConfigA = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
|
||||
ConfigMapRef: &api.ObjectReference{Name: "foo", Namespace: "bar", UID: "fooUID"}}}}
|
||||
mynodeObjConfigB = &api.Node{ObjectMeta: mynodeObjMeta, Spec: api.NodeSpec{ConfigSource: &api.NodeConfigSource{
|
||||
ConfigMapRef: &api.ObjectReference{Name: "qux", Namespace: "bar", UID: "quxUID"}}}}
|
||||
othernodeObj = &api.Node{ObjectMeta: metav1.ObjectMeta{Name: "othernode"}}
|
||||
|
||||
mymirrorpod = makeTestPod("ns", "mymirrorpod", "mynode", true)
|
||||
othermirrorpod = makeTestPod("ns", "othermirrorpod", "othernode", true)
|
||||
unboundmirrorpod = makeTestPod("ns", "unboundmirrorpod", "", true)
|
||||
mypod = makeTestPod("ns", "mypod", "mynode", false)
|
||||
otherpod = makeTestPod("ns", "otherpod", "othernode", false)
|
||||
unboundpod = makeTestPod("ns", "unboundpod", "", false)
|
||||
unnamedpod = makeTestPod("ns", "", "mynode", false)
|
||||
|
||||
mymirrorpodEviction = makeTestPodEviction("mymirrorpod")
|
||||
othermirrorpodEviction = makeTestPodEviction("othermirrorpod")
|
||||
unboundmirrorpodEviction = makeTestPodEviction("unboundmirrorpod")
|
||||
mypodEviction = makeTestPodEviction("mypod")
|
||||
otherpodEviction = makeTestPodEviction("otherpod")
|
||||
unboundpodEviction = makeTestPodEviction("unboundpod")
|
||||
unnamedEviction = makeTestPodEviction("")
|
||||
|
||||
configmapResource = api.Resource("configmap").WithVersion("v1")
|
||||
configmapKind = api.Kind("ConfigMap").WithVersion("v1")
|
||||
|
||||
podResource = api.Resource("pods").WithVersion("v1")
|
||||
podKind = api.Kind("Pod").WithVersion("v1")
|
||||
evictionKind = policyapi.Kind("Eviction").WithVersion("v1beta1")
|
||||
|
||||
nodeResource = api.Resource("nodes").WithVersion("v1")
|
||||
nodeKind = api.Kind("Node").WithVersion("v1")
|
||||
|
||||
noExistingPods = fake.NewSimpleClientset().Core()
|
||||
existingPods = fake.NewSimpleClientset(mymirrorpod, othermirrorpod, unboundmirrorpod, mypod, otherpod, unboundpod).Core()
|
||||
)
|
||||
|
||||
sapod := makeTestPod("ns", "mysapod", "mynode", true)
|
||||
sapod.Spec.ServiceAccountName = "foo"
|
||||
|
||||
secretpod := makeTestPod("ns", "mysecretpod", "mynode", true)
|
||||
secretpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}}}
|
||||
|
||||
configmappod := makeTestPod("ns", "myconfigmappod", "mynode", true)
|
||||
configmappod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{ConfigMap: &api.ConfigMapVolumeSource{LocalObjectReference: api.LocalObjectReference{Name: "foo"}}}}}
|
||||
|
||||
pvcpod := makeTestPod("ns", "mypvcpod", "mynode", true)
|
||||
pvcpod.Spec.Volumes = []api.Volume{{VolumeSource: api.VolumeSource{PersistentVolumeClaim: &api.PersistentVolumeClaimVolumeSource{ClaimName: "foo"}}}}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
podsGetter coreinternalversion.PodsGetter
|
||||
attributes admission.Attributes
|
||||
err string
|
||||
}{
|
||||
// Mirror pods bound to us
|
||||
{
|
||||
name: "allow creating a mirror pod bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, mymirrorpod, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow delete of mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid create of mirror pod status bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow update of mirror pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpod, mymirrorpod, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow create of eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mymirrorpodEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow create of unnamed eviction for mirror pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mymirrorpod.Namespace, mymirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Mirror pods bound to another node
|
||||
{
|
||||
name: "forbid creating a mirror pod bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, othermirrorpod, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of mirror pod status bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpod, othermirrorpod, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for mirror pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othermirrorpodEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for mirror pod to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, othermirrorpod.Namespace, othermirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Mirror pods not bound to any node
|
||||
{
|
||||
name: "forbid creating a mirror pod unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, unboundmirrorpod, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of mirror pod status unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of mirror pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpod, unboundmirrorpod, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of mirror pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundmirrorpodEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for mirror pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unboundmirrorpod.Namespace, unboundmirrorpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Normal pods bound to us
|
||||
{
|
||||
name: "forbid creating a normal pod bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, nil, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "can only create mirror pods",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, mypod, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow delete of normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid create of normal pod status bound to self",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow update of normal pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypod, mypod, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod status bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, mypod.Namespace, mypod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "allow create of unnamed eviction for normal pod bound to self",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Normal pods bound to another
|
||||
{
|
||||
name: "forbid creating a normal pod bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "can only create mirror pods",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, otherpod, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of normal pod status bound to another",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpod, otherpod, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod status bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, otherpod.Namespace, otherpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(otherpodEviction, nil, evictionKind, otherpodEviction.Namespace, otherpodEviction.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for normal pod bound to another",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, otherpod.Namespace, otherpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Normal pods not bound to any node
|
||||
{
|
||||
name: "forbid creating a normal pod unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "can only create mirror pods",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid create of normal pod status unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Create, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid update of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Update, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpodEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for normal unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unboundpod.Namespace, unboundpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "spec.nodeName set to itself",
|
||||
},
|
||||
|
||||
// Missing pod
|
||||
{
|
||||
name: "forbid delete of unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
{
|
||||
name: "forbid create of eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for unknown pod",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, mypod.Namespace, mypod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "not found",
|
||||
},
|
||||
|
||||
// Eviction for unnamed pod
|
||||
{
|
||||
name: "allow create of eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
// use the submitted eviction resource name as the pod name
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid update of eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Update, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mypodEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Delete, mynode),
|
||||
err: "forbidden: unexpected operation",
|
||||
},
|
||||
{
|
||||
name: "forbid create of unnamed eviction for unnamed pod",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unnamedEviction, nil, evictionKind, unnamedpod.Namespace, unnamedpod.Name, podResource, "eviction", admission.Create, mynode),
|
||||
err: "could not determine pod from request data",
|
||||
},
|
||||
|
||||
// Resource pods
|
||||
{
|
||||
name: "forbid create of pod referencing service account",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(sapod, nil, podKind, sapod.Namespace, sapod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference a service account",
|
||||
},
|
||||
{
|
||||
name: "forbid create of pod referencing secret",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(secretpod, nil, podKind, secretpod.Namespace, secretpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference secrets",
|
||||
},
|
||||
{
|
||||
name: "forbid create of pod referencing configmap",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(configmappod, nil, podKind, configmappod.Namespace, configmappod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference configmaps",
|
||||
},
|
||||
{
|
||||
name: "forbid create of pod referencing persistentvolumeclaim",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(pvcpod, nil, podKind, pvcpod.Namespace, pvcpod.Name, podResource, "", admission.Create, mynode),
|
||||
err: "reference persistentvolumeclaims",
|
||||
},
|
||||
|
||||
// My node object
|
||||
{
|
||||
name: "allow create of my node",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow create of my node pulling name from object",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, nil, nodeKind, mynodeObj.Namespace, "", nodeResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow delete of my node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node status",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "status", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "forbid create of my node with non-nil configSource",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigA, nil, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Create, mynode),
|
||||
err: "create with non-nil configSource",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: nil configSource to new non-nil configSource",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigA, mynodeObj, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "update configSource to a new non-nil configSource",
|
||||
},
|
||||
{
|
||||
name: "forbid update of my node: non-nil configSource to new non-nil configSource",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigB, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "update configSource to a new non-nil configSource",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node: non-nil configSource unchanged",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObjConfigA, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of my node: non-nil configSource to nil configSource",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(mynodeObj, mynodeObjConfigA, nodeKind, mynodeObj.Namespace, mynodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Other node object
|
||||
{
|
||||
name: "forbid create of other node",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Create, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid create of other node pulling name from object",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, nil, nodeKind, othernodeObj.Namespace, "", nodeResource, "", admission.Create, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid update of other node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Update, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid delete of other node",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "", admission.Delete, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
{
|
||||
name: "forbid update of other node status",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(othernodeObj, othernodeObj, nodeKind, othernodeObj.Namespace, othernodeObj.Name, nodeResource, "status", admission.Update, mynode),
|
||||
err: "cannot modify node",
|
||||
},
|
||||
|
||||
// Unrelated objects
|
||||
{
|
||||
name: "allow create of unrelated object",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(&api.ConfigMap{}, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Create, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow update of unrelated object",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(&api.ConfigMap{}, &api.ConfigMap{}, configmapKind, "myns", "mycm", configmapResource, "", admission.Update, mynode),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow delete of unrelated object",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, configmapKind, "myns", "mycm", configmapResource, "", admission.Delete, mynode),
|
||||
err: "",
|
||||
},
|
||||
|
||||
// Unrelated user
|
||||
{
|
||||
name: "allow unrelated user creating a normal pod unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Create, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user update of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Update, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user delete of normal pod unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "", admission.Delete, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user create of normal pod status unbound",
|
||||
podsGetter: noExistingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Create, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user update of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(unboundpod, unboundpod, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Update, bob),
|
||||
err: "",
|
||||
},
|
||||
{
|
||||
name: "allow unrelated user delete of normal pod status unbound",
|
||||
podsGetter: existingPods,
|
||||
attributes: admission.NewAttributesRecord(nil, nil, podKind, unboundpod.Namespace, unboundpod.Name, podResource, "status", admission.Delete, bob),
|
||||
err: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := NewPlugin(nodeidentifier.NewDefaultNodeIdentifier())
|
||||
c.podsGetter = tt.podsGetter
|
||||
err := c.Admit(tt.attributes)
|
||||
if (err == nil) != (len(tt.err) == 0) {
|
||||
t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, tt.err)
|
||||
return
|
||||
}
|
||||
if len(tt.err) > 0 && !strings.Contains(err.Error(), tt.err) {
|
||||
t.Errorf("nodePlugin.Admit() error = %v, expected %v", err, tt.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/BUILD
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/BUILD
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
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",
|
||||
"doc.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/cloudprovider:go_default_library",
|
||||
"//pkg/cloudprovider/providers/aws:go_default_library",
|
||||
"//pkg/cloudprovider/providers/gce:go_default_library",
|
||||
"//pkg/kubeapiserver/admission:go_default_library",
|
||||
"//pkg/kubelet/apis:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/cloudprovider/providers/aws: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/apimachinery/pkg/types: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"],
|
||||
)
|
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission.go
generated
vendored
Normal file
216
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission.go
generated
vendored
Normal file
@ -0,0 +1,216 @@
|
||||
/*
|
||||
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 label
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
||||
vol "k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register("PersistentVolumeLabel", func(config io.Reader) (admission.Interface, error) {
|
||||
persistentVolumeLabelAdmission := NewPersistentVolumeLabel()
|
||||
return persistentVolumeLabelAdmission, nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = admission.Interface(&persistentVolumeLabel{})
|
||||
|
||||
type persistentVolumeLabel struct {
|
||||
*admission.Handler
|
||||
|
||||
mutex sync.Mutex
|
||||
ebsVolumes aws.Volumes
|
||||
cloudConfig []byte
|
||||
gceCloudProvider *gce.GCECloud
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &persistentVolumeLabel{}
|
||||
var _ kubeapiserveradmission.WantsCloudConfig = &persistentVolumeLabel{}
|
||||
|
||||
// NewPersistentVolumeLabel returns an admission.Interface implementation which adds labels to PersistentVolume CREATE requests,
|
||||
// based on the labels provided by the underlying cloud provider.
|
||||
//
|
||||
// As a side effect, the cloud provider may block invalid or non-existent volumes.
|
||||
func NewPersistentVolumeLabel() *persistentVolumeLabel {
|
||||
// DEPRECATED: cloud-controller-manager will now start NewPersistentVolumeLabelController
|
||||
// which does exactly what this admission controller used to do. So once GCE and AWS can
|
||||
// run externally, we can remove this admission controller.
|
||||
glog.Warning("PersistentVolumeLabel admission controller is deprecated. " +
|
||||
"Please remove this controller from your configuration files and scripts.")
|
||||
return &persistentVolumeLabel{
|
||||
Handler: admission.NewHandler(admission.Create),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) SetCloudConfig(cloudConfig []byte) {
|
||||
l.cloudConfig = cloudConfig
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) Admit(a admission.Attributes) (err error) {
|
||||
if a.GetResource().GroupResource() != api.Resource("persistentvolumes") {
|
||||
return nil
|
||||
}
|
||||
obj := a.GetObject()
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
volume, ok := obj.(*api.PersistentVolume)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var volumeLabels map[string]string
|
||||
if volume.Spec.AWSElasticBlockStore != nil {
|
||||
labels, err := l.findAWSEBSLabels(volume)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("error querying AWS EBS volume %s: %v", volume.Spec.AWSElasticBlockStore.VolumeID, err))
|
||||
}
|
||||
volumeLabels = labels
|
||||
}
|
||||
if volume.Spec.GCEPersistentDisk != nil {
|
||||
labels, err := l.findGCEPDLabels(volume)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("error querying GCE PD volume %s: %v", volume.Spec.GCEPersistentDisk.PDName, err))
|
||||
}
|
||||
volumeLabels = labels
|
||||
}
|
||||
|
||||
if len(volumeLabels) != 0 {
|
||||
if volume.Labels == nil {
|
||||
volume.Labels = make(map[string]string)
|
||||
}
|
||||
for k, v := range volumeLabels {
|
||||
// We (silently) replace labels if they are provided.
|
||||
// This should be OK because they are in the kubernetes.io namespace
|
||||
// i.e. we own them
|
||||
volume.Labels[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) findAWSEBSLabels(volume *api.PersistentVolume) (map[string]string, error) {
|
||||
// Ignore any volumes that are being provisioned
|
||||
if volume.Spec.AWSElasticBlockStore.VolumeID == vol.ProvisionedVolumeName {
|
||||
return nil, nil
|
||||
}
|
||||
ebsVolumes, err := l.getEBSVolumes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ebsVolumes == nil {
|
||||
return nil, fmt.Errorf("unable to build AWS cloud provider for EBS")
|
||||
}
|
||||
|
||||
// TODO: GetVolumeLabels is actually a method on the Volumes interface
|
||||
// If that gets standardized we can refactor to reduce code duplication
|
||||
spec := aws.KubernetesVolumeID(volume.Spec.AWSElasticBlockStore.VolumeID)
|
||||
labels, err := ebsVolumes.GetVolumeLabels(spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// getEBSVolumes returns the AWS Volumes interface for ebs
|
||||
func (l *persistentVolumeLabel) getEBSVolumes() (aws.Volumes, error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.ebsVolumes == nil {
|
||||
var cloudConfigReader io.Reader
|
||||
if len(l.cloudConfig) > 0 {
|
||||
cloudConfigReader = bytes.NewReader(l.cloudConfig)
|
||||
}
|
||||
cloudProvider, err := cloudprovider.GetCloudProvider("aws", cloudConfigReader)
|
||||
if err != nil || cloudProvider == nil {
|
||||
return nil, err
|
||||
}
|
||||
awsCloudProvider, ok := cloudProvider.(*aws.Cloud)
|
||||
if !ok {
|
||||
// GetCloudProvider has gone very wrong
|
||||
return nil, fmt.Errorf("error retrieving AWS cloud provider")
|
||||
}
|
||||
l.ebsVolumes = awsCloudProvider
|
||||
}
|
||||
return l.ebsVolumes, nil
|
||||
}
|
||||
|
||||
func (l *persistentVolumeLabel) findGCEPDLabels(volume *api.PersistentVolume) (map[string]string, error) {
|
||||
// Ignore any volumes that are being provisioned
|
||||
if volume.Spec.GCEPersistentDisk.PDName == vol.ProvisionedVolumeName {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
provider, err := l.getGCECloudProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if provider == nil {
|
||||
return nil, fmt.Errorf("unable to build GCE cloud provider for PD")
|
||||
}
|
||||
|
||||
// If the zone is already labeled, honor the hint
|
||||
zone := volume.Labels[kubeletapis.LabelZoneFailureDomain]
|
||||
|
||||
labels, err := provider.GetAutoLabelsForPD(volume.Spec.GCEPersistentDisk.PDName, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return labels, nil
|
||||
}
|
||||
|
||||
// getGCECloudProvider returns the GCE cloud provider, for use for querying volume labels
|
||||
func (l *persistentVolumeLabel) getGCECloudProvider() (*gce.GCECloud, error) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
if l.gceCloudProvider == nil {
|
||||
var cloudConfigReader io.Reader
|
||||
if len(l.cloudConfig) > 0 {
|
||||
cloudConfigReader = bytes.NewReader(l.cloudConfig)
|
||||
}
|
||||
cloudProvider, err := cloudprovider.GetCloudProvider("gce", cloudConfigReader)
|
||||
if err != nil || cloudProvider == nil {
|
||||
return nil, err
|
||||
}
|
||||
gceCloudProvider, ok := cloudProvider.(*gce.GCECloud)
|
||||
if !ok {
|
||||
// GetCloudProvider has gone very wrong
|
||||
return nil, fmt.Errorf("error retrieving GCE cloud provider")
|
||||
}
|
||||
l.gceCloudProvider = gceCloudProvider
|
||||
}
|
||||
return l.gceCloudProvider, nil
|
||||
}
|
176
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission_test.go
generated
vendored
Normal file
176
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/admission_test.go
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
/*
|
||||
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 label
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
|
||||
)
|
||||
|
||||
type mockVolumes struct {
|
||||
volumeLabels map[string]string
|
||||
volumeLabelsError error
|
||||
}
|
||||
|
||||
var _ aws.Volumes = &mockVolumes{}
|
||||
|
||||
func (v *mockVolumes) AttachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName, readOnly bool) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) DetachDisk(diskName aws.KubernetesVolumeID, nodeName types.NodeName) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) CreateDisk(volumeOptions *aws.VolumeOptions) (volumeName aws.KubernetesVolumeID, err error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) DeleteDisk(volumeName aws.KubernetesVolumeID) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (v *mockVolumes) GetVolumeLabels(volumeName aws.KubernetesVolumeID) (map[string]string, error) {
|
||||
return v.volumeLabels, v.volumeLabelsError
|
||||
}
|
||||
|
||||
func (c *mockVolumes) GetDiskPath(volumeName aws.KubernetesVolumeID) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) DiskIsAttached(volumeName aws.KubernetesVolumeID, nodeName types.NodeName) (bool, error) {
|
||||
return false, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) DisksAreAttached(nodeDisks map[types.NodeName][]aws.KubernetesVolumeID) (map[types.NodeName]map[aws.KubernetesVolumeID]bool, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (c *mockVolumes) ResizeDisk(
|
||||
diskName aws.KubernetesVolumeID,
|
||||
oldSize resource.Quantity,
|
||||
newSize resource.Quantity) (resource.Quantity, error) {
|
||||
return oldSize, nil
|
||||
}
|
||||
|
||||
func mockVolumeFailure(err error) *mockVolumes {
|
||||
return &mockVolumes{volumeLabelsError: err}
|
||||
}
|
||||
|
||||
func mockVolumeLabels(labels map[string]string) *mockVolumes {
|
||||
return &mockVolumes{volumeLabels: labels}
|
||||
}
|
||||
|
||||
// TestAdmission
|
||||
func TestAdmission(t *testing.T) {
|
||||
pvHandler := NewPersistentVolumeLabel()
|
||||
handler := admission.NewChainHandler(pvHandler)
|
||||
ignoredPV := api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
awsPV := api.PersistentVolume{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "noncloud", Namespace: "myns"},
|
||||
Spec: api.PersistentVolumeSpec{
|
||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
||||
AWSElasticBlockStore: &api.AWSElasticBlockStoreVolumeSource{
|
||||
VolumeID: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Non-cloud PVs are ignored
|
||||
err := handler.Admit(admission.NewAttributesRecord(&ignoredPV, nil, api.Kind("PersistentVolume").WithVersion("version"), ignoredPV.Namespace, ignoredPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler (on ignored pv): %v", err)
|
||||
}
|
||||
|
||||
// We only add labels on creation
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Delete, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler (when deleting aws pv): %v", err)
|
||||
}
|
||||
|
||||
// Errors from the cloudprovider block creation of the volume
|
||||
pvHandler.ebsVolumes = mockVolumeFailure(fmt.Errorf("invalid volume"))
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when aws pv info fails")
|
||||
}
|
||||
|
||||
// Don't add labels if the cloudprovider doesn't return any
|
||||
labels := make(map[string]string)
|
||||
pvHandler.ebsVolumes = mockVolumeLabels(labels)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when creating aws pv")
|
||||
}
|
||||
if len(awsPV.ObjectMeta.Labels) != 0 {
|
||||
t.Errorf("Unexpected number of labels")
|
||||
}
|
||||
|
||||
// Don't panic if the cloudprovider returns nil, nil
|
||||
pvHandler.ebsVolumes = mockVolumeFailure(nil)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when cloud provider returns empty labels")
|
||||
}
|
||||
|
||||
// Labels from the cloudprovider should be applied to the volume
|
||||
labels = make(map[string]string)
|
||||
labels["a"] = "1"
|
||||
labels["b"] = "2"
|
||||
pvHandler.ebsVolumes = mockVolumeLabels(labels)
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when creating aws pv")
|
||||
}
|
||||
if awsPV.Labels["a"] != "1" || awsPV.Labels["b"] != "2" {
|
||||
t.Errorf("Expected label a to be added when creating aws pv")
|
||||
}
|
||||
|
||||
// User-provided labels should be honored, but cloudprovider labels replace them when they overlap
|
||||
awsPV.ObjectMeta.Labels = make(map[string]string)
|
||||
awsPV.ObjectMeta.Labels["a"] = "not1"
|
||||
awsPV.ObjectMeta.Labels["c"] = "3"
|
||||
err = handler.Admit(admission.NewAttributesRecord(&awsPV, nil, api.Kind("PersistentVolume").WithVersion("version"), awsPV.Namespace, awsPV.Name, api.Resource("persistentvolumes").WithVersion("version"), "", admission.Create, nil))
|
||||
if err != nil {
|
||||
t.Errorf("Expected no error when creating aws pv")
|
||||
}
|
||||
if awsPV.Labels["a"] != "1" || awsPV.Labels["b"] != "2" {
|
||||
t.Errorf("Expected cloudprovider labels to replace user labels when creating aws pv")
|
||||
}
|
||||
if awsPV.Labels["c"] != "3" {
|
||||
t.Errorf("Expected (non-conflicting) user provided labels to be honored when creating aws pv")
|
||||
}
|
||||
|
||||
}
|
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label/doc.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
Copyright 2014 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.
|
||||
*/
|
||||
|
||||
// labels created persistent volumes with zone information
|
||||
// as provided by the cloud provider
|
||||
package label // import "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label"
|
53
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize/BUILD
generated
vendored
Normal file
53
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize/BUILD
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/controller: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/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
|
||||
"//pkg/client/listers/core/internalversion:go_default_library",
|
||||
"//pkg/client/listers/storage/internalversion:go_default_library",
|
||||
"//pkg/kubeapiserver/admission: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"],
|
||||
)
|
165
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize/admission.go
generated
vendored
Normal file
165
vendor/k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/resize/admission.go
generated
vendored
Normal file
@ -0,0 +1,165 @@
|
||||
/*
|
||||
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 resize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
apihelper "k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
pvlister "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
|
||||
storagelisters "k8s.io/kubernetes/pkg/client/listers/storage/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName is the name of pvc resize admission plugin
|
||||
PluginName = "PersistentVolumeClaimResize"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
plugin := newPlugin()
|
||||
return plugin, nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ admission.Interface = &persistentVolumeClaimResize{}
|
||||
var _ admission.ValidationInterface = &persistentVolumeClaimResize{}
|
||||
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&persistentVolumeClaimResize{})
|
||||
|
||||
type persistentVolumeClaimResize struct {
|
||||
*admission.Handler
|
||||
|
||||
pvLister pvlister.PersistentVolumeLister
|
||||
scLister storagelisters.StorageClassLister
|
||||
}
|
||||
|
||||
func newPlugin() *persistentVolumeClaimResize {
|
||||
return &persistentVolumeClaimResize{
|
||||
Handler: admission.NewHandler(admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
func (pvcr *persistentVolumeClaimResize) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
pvcInformer := f.Core().InternalVersion().PersistentVolumes()
|
||||
pvcr.pvLister = pvcInformer.Lister()
|
||||
scInformer := f.Storage().InternalVersion().StorageClasses()
|
||||
pvcr.scLister = scInformer.Lister()
|
||||
pvcr.SetReadyFunc(func() bool {
|
||||
return pvcInformer.Informer().HasSynced() && scInformer.Informer().HasSynced()
|
||||
})
|
||||
}
|
||||
|
||||
// ValidateInitialization ensures lister is set.
|
||||
func (pvcr *persistentVolumeClaimResize) ValidateInitialization() error {
|
||||
if pvcr.pvLister == nil {
|
||||
return fmt.Errorf("missing persistent volume lister")
|
||||
}
|
||||
if pvcr.scLister == nil {
|
||||
return fmt.Errorf("missing storageclass lister")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pvcr *persistentVolumeClaimResize) Validate(a admission.Attributes) error {
|
||||
if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(a.GetSubresource()) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
pvc, ok := a.GetObject().(*api.PersistentVolumeClaim)
|
||||
// if we can't convert then we don't handle this object so just return
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
oldPvc, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldSize := oldPvc.Spec.Resources.Requests[api.ResourceStorage]
|
||||
newSize := pvc.Spec.Resources.Requests[api.ResourceStorage]
|
||||
|
||||
if newSize.Cmp(oldSize) <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if oldPvc.Status.Phase != api.ClaimBound {
|
||||
return admission.NewForbidden(a, fmt.Errorf("Only bound persistent volume claims can be expanded"))
|
||||
}
|
||||
|
||||
// Growing Persistent volumes is only allowed for PVCs for which their StorageClass
|
||||
// explicitly allows it
|
||||
if !pvcr.allowResize(pvc, oldPvc) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("only dynamically provisioned pvc can be resized and "+
|
||||
"the storageclass that provisions the pvc must support resize"))
|
||||
}
|
||||
|
||||
// volume plugin must support resize
|
||||
pv, err := pvcr.pvLister.Get(pvc.Spec.VolumeName)
|
||||
if err != nil {
|
||||
return admission.NewForbidden(a, fmt.Errorf("Error updating persistent volume claim because fetching associated persistent volume failed"))
|
||||
}
|
||||
|
||||
if !pvcr.checkVolumePlugin(pv) {
|
||||
return admission.NewForbidden(a, fmt.Errorf("volume plugin does not support resize"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Growing Persistent volumes is only allowed for PVCs for which their StorageClass
|
||||
// explicitly allows it.
|
||||
func (pvcr *persistentVolumeClaimResize) allowResize(pvc, oldPvc *api.PersistentVolumeClaim) bool {
|
||||
pvcStorageClass := apihelper.GetPersistentVolumeClaimClass(pvc)
|
||||
oldPvcStorageClass := apihelper.GetPersistentVolumeClaimClass(oldPvc)
|
||||
if pvcStorageClass == "" || oldPvcStorageClass == "" || pvcStorageClass != oldPvcStorageClass {
|
||||
return false
|
||||
}
|
||||
sc, err := pvcr.scLister.Get(pvcStorageClass)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if sc.AllowVolumeExpansion != nil {
|
||||
return *sc.AllowVolumeExpansion
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkVolumePlugin checks whether the volume plugin supports resize
|
||||
func (pvcr *persistentVolumeClaimResize) checkVolumePlugin(pv *api.PersistentVolume) bool {
|
||||
if pv.Spec.Glusterfs != nil || pv.Spec.Cinder != nil || pv.Spec.RBD != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if pv.Spec.GCEPersistentDisk != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if pv.Spec.AWSElasticBlockStore != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user