mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 04:33:26 +00:00
Add decoding of nested scheduling plugin configs
Signed-off-by: Aldo Culquicondor <acondor@google.com>
This commit is contained in:
parent
7814f3aaf7
commit
6b153dc920
@ -1,4 +1,4 @@
|
|||||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
@ -28,3 +28,19 @@ filegroup(
|
|||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["scheme_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//pkg/scheduler/apis/config:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/kube-scheduler/config/v1alpha2:go_default_library",
|
||||||
|
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
450
pkg/scheduler/apis/config/scheme/scheme_test.go
Normal file
450
pkg/scheduler/apis/config/scheme/scheme_test.go
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 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 scheme
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/kube-scheduler/config/v1alpha2"
|
||||||
|
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodecsDecodePluginConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
wantErr string
|
||||||
|
wantProfiles []config.KubeSchedulerProfile
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "v1alpha2 all plugin args in default profile",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- name: InterPodAffinity
|
||||||
|
args:
|
||||||
|
hardPodAffinityWeight: 5
|
||||||
|
- name: NodeLabel
|
||||||
|
args:
|
||||||
|
presentLabels: ["foo"]
|
||||||
|
- name: NodeResourcesFit
|
||||||
|
args:
|
||||||
|
ignoredResources: ["foo"]
|
||||||
|
- name: RequestedToCapacityRatio
|
||||||
|
args:
|
||||||
|
shape:
|
||||||
|
- utilization: 1
|
||||||
|
- name: PodTopologySpread
|
||||||
|
args:
|
||||||
|
defaultConstraints:
|
||||||
|
- maxSkew: 1
|
||||||
|
topologyKey: zone
|
||||||
|
whenUnsatisfiable: ScheduleAnyway
|
||||||
|
- name: ServiceAffinity
|
||||||
|
args:
|
||||||
|
affinityLabels: ["bar"]
|
||||||
|
`),
|
||||||
|
wantProfiles: []config.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
SchedulerName: "default-scheduler",
|
||||||
|
PluginConfig: []config.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "InterPodAffinity",
|
||||||
|
Args: &config.InterPodAffinityArgs{HardPodAffinityWeight: 5},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NodeLabel",
|
||||||
|
Args: &config.NodeLabelArgs{PresentLabels: []string{"foo"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NodeResourcesFit",
|
||||||
|
Args: &config.NodeResourcesFitArgs{IgnoredResources: []string{"foo"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "RequestedToCapacityRatio",
|
||||||
|
Args: &config.RequestedToCapacityRatioArgs{
|
||||||
|
Shape: []config.UtilizationShapePoint{{Utilization: 1}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "PodTopologySpread",
|
||||||
|
Args: &config.PodTopologySpreadArgs{
|
||||||
|
DefaultConstraints: []v1.TopologySpreadConstraint{
|
||||||
|
{MaxSkew: 1, TopologyKey: "zone", WhenUnsatisfiable: v1.ScheduleAnyway},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "ServiceAffinity",
|
||||||
|
Args: &config.ServiceAffinityArgs{
|
||||||
|
AffinityLabels: []string{"bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1alpha2 plugins can include version and kind",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- name: NodeLabel
|
||||||
|
args:
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: NodeLabelArgs
|
||||||
|
presentLabels: ["bars"]
|
||||||
|
`),
|
||||||
|
wantProfiles: []config.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
SchedulerName: "default-scheduler",
|
||||||
|
PluginConfig: []config.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "NodeLabel",
|
||||||
|
Args: &config.NodeLabelArgs{PresentLabels: []string{"bars"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plugin group and kind should match the type",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- name: NodeLabel
|
||||||
|
args:
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: InterPodAffinityArgs
|
||||||
|
`),
|
||||||
|
wantErr: "decoding .profiles[0].pluginConfig[0]: args for plugin NodeLabel were not of type NodeLabelArgs.kubescheduler.config.k8s.io, got InterPodAffinityArgs.kubescheduler.config.k8s.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// TODO: do not replicate this case for v1beta1.
|
||||||
|
name: "v1alpha2 case insensitive RequestedToCapacityRatioArgs",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- name: RequestedToCapacityRatio
|
||||||
|
args:
|
||||||
|
shape:
|
||||||
|
- utilization: 1
|
||||||
|
score: 2
|
||||||
|
- Utilization: 3
|
||||||
|
Score: 4
|
||||||
|
resources:
|
||||||
|
- name: Upper
|
||||||
|
weight: 1
|
||||||
|
- Name: lower
|
||||||
|
weight: 2
|
||||||
|
`),
|
||||||
|
wantProfiles: []config.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
SchedulerName: "default-scheduler",
|
||||||
|
PluginConfig: []config.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "RequestedToCapacityRatio",
|
||||||
|
Args: &config.RequestedToCapacityRatioArgs{
|
||||||
|
Shape: []config.UtilizationShapePoint{
|
||||||
|
{Utilization: 1, Score: 2},
|
||||||
|
{Utilization: 3, Score: 4},
|
||||||
|
},
|
||||||
|
Resources: []config.ResourceSpec{
|
||||||
|
{Name: "Upper", Weight: 1},
|
||||||
|
{Name: "lower", Weight: 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "out-of-tree plugin args",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- name: OutOfTreePlugin
|
||||||
|
args:
|
||||||
|
foo: bar
|
||||||
|
`),
|
||||||
|
wantProfiles: []config.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
SchedulerName: "default-scheduler",
|
||||||
|
PluginConfig: []config.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "OutOfTreePlugin",
|
||||||
|
Args: &runtime.Unknown{
|
||||||
|
ContentType: "application/json",
|
||||||
|
Raw: []byte(`{"foo":"bar"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty and no plugin args",
|
||||||
|
data: []byte(`
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- name: InterPodAffinity
|
||||||
|
args:
|
||||||
|
- name: NodeResourcesFit
|
||||||
|
- name: OutOfTreePlugin
|
||||||
|
args:
|
||||||
|
`),
|
||||||
|
wantProfiles: []config.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
SchedulerName: "default-scheduler",
|
||||||
|
PluginConfig: []config.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "InterPodAffinity",
|
||||||
|
// TODO(acondor): Set default values.
|
||||||
|
Args: &config.InterPodAffinityArgs{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "NodeResourcesFit",
|
||||||
|
Args: &config.NodeResourcesFitArgs{},
|
||||||
|
},
|
||||||
|
{Name: "OutOfTreePlugin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
decoder := Codecs.UniversalDecoder()
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
obj, gvk, err := decoder.Decode(tt.data, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
if tt.wantErr != err.Error() {
|
||||||
|
t.Fatalf("got err %w, want %w", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(tt.wantErr) != 0 {
|
||||||
|
t.Fatal("no error produced, wanted %w", tt.wantErr)
|
||||||
|
}
|
||||||
|
got, ok := obj.(*config.KubeSchedulerConfiguration)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("decoded into %s, want %s", gvk, config.SchemeGroupVersion.WithKind("KubeSchedulerConfiguration"))
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tt.wantProfiles, got.Profiles); diff != "" {
|
||||||
|
t.Errorf("unexpected configuration (-want,+got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodecsEncodePluginConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
obj runtime.Object
|
||||||
|
version schema.GroupVersion
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "v1alpha2 in-tree and out-of-tree plugins",
|
||||||
|
version: v1alpha2.SchemeGroupVersion,
|
||||||
|
obj: &v1alpha2.KubeSchedulerConfiguration{
|
||||||
|
Profiles: []v1alpha2.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
PluginConfig: []v1alpha2.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "InterPodAffinity",
|
||||||
|
Args: runtime.RawExtension{
|
||||||
|
Object: &v1alpha2.InterPodAffinityArgs{
|
||||||
|
HardPodAffinityWeight: pointer.Int32Ptr(5),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "RequestedToCapacityRatio",
|
||||||
|
Args: runtime.RawExtension{
|
||||||
|
Object: &v1alpha2.RequestedToCapacityRatioArgs{
|
||||||
|
Shape: []v1alpha2.UtilizationShapePoint{
|
||||||
|
{Utilization: 1, Score: 2},
|
||||||
|
},
|
||||||
|
Resources: []v1alpha2.ResourceSpec{
|
||||||
|
{Name: "lower", Weight: 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "OutOfTreePlugin",
|
||||||
|
Args: runtime.RawExtension{
|
||||||
|
Raw: []byte(`{"foo":"bar"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: `apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
clientConnection:
|
||||||
|
acceptContentTypes: ""
|
||||||
|
burst: 0
|
||||||
|
contentType: ""
|
||||||
|
kubeconfig: ""
|
||||||
|
qps: 0
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
leaderElection:
|
||||||
|
leaderElect: null
|
||||||
|
leaseDuration: 0s
|
||||||
|
renewDeadline: 0s
|
||||||
|
resourceLock: ""
|
||||||
|
resourceName: ""
|
||||||
|
resourceNamespace: ""
|
||||||
|
retryPeriod: 0s
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- args:
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
hardPodAffinityWeight: 5
|
||||||
|
kind: InterPodAffinityArgs
|
||||||
|
name: InterPodAffinity
|
||||||
|
- args:
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
kind: RequestedToCapacityRatioArgs
|
||||||
|
resources:
|
||||||
|
- Name: lower
|
||||||
|
Weight: 2
|
||||||
|
shape:
|
||||||
|
- Score: 2
|
||||||
|
Utilization: 1
|
||||||
|
name: RequestedToCapacityRatio
|
||||||
|
- args:
|
||||||
|
foo: bar
|
||||||
|
name: OutOfTreePlugin
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1alpha2 in-tree and out-of-tree plugins from internal",
|
||||||
|
version: v1alpha2.SchemeGroupVersion,
|
||||||
|
obj: &config.KubeSchedulerConfiguration{
|
||||||
|
Profiles: []config.KubeSchedulerProfile{
|
||||||
|
{
|
||||||
|
PluginConfig: []config.PluginConfig{
|
||||||
|
{
|
||||||
|
Name: "InterPodAffinity",
|
||||||
|
Args: &config.InterPodAffinityArgs{
|
||||||
|
HardPodAffinityWeight: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "OutOfTreePlugin",
|
||||||
|
Args: &runtime.Unknown{
|
||||||
|
Raw: []byte(`{"foo":"bar"}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: `apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
bindTimeoutSeconds: 0
|
||||||
|
clientConnection:
|
||||||
|
acceptContentTypes: ""
|
||||||
|
burst: 0
|
||||||
|
contentType: ""
|
||||||
|
kubeconfig: ""
|
||||||
|
qps: 0
|
||||||
|
disablePreemption: false
|
||||||
|
enableContentionProfiling: false
|
||||||
|
enableProfiling: false
|
||||||
|
healthzBindAddress: ""
|
||||||
|
kind: KubeSchedulerConfiguration
|
||||||
|
leaderElection:
|
||||||
|
leaderElect: false
|
||||||
|
leaseDuration: 0s
|
||||||
|
renewDeadline: 0s
|
||||||
|
resourceLock: ""
|
||||||
|
resourceName: ""
|
||||||
|
resourceNamespace: ""
|
||||||
|
retryPeriod: 0s
|
||||||
|
metricsBindAddress: ""
|
||||||
|
percentageOfNodesToScore: 0
|
||||||
|
podInitialBackoffSeconds: 0
|
||||||
|
podMaxBackoffSeconds: 0
|
||||||
|
profiles:
|
||||||
|
- pluginConfig:
|
||||||
|
- args:
|
||||||
|
apiVersion: kubescheduler.config.k8s.io/v1alpha2
|
||||||
|
hardPodAffinityWeight: 5
|
||||||
|
kind: InterPodAffinityArgs
|
||||||
|
name: InterPodAffinity
|
||||||
|
- args:
|
||||||
|
foo: bar
|
||||||
|
name: OutOfTreePlugin
|
||||||
|
schedulerName: ""
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
yamlInfo, ok := runtime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), runtime.ContentTypeYAML)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unable to locate encoder -- %q is not a supported media type", runtime.ContentTypeYAML)
|
||||||
|
}
|
||||||
|
jsonInfo, ok := runtime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unable to locate encoder -- %q is not a supported media type", runtime.ContentTypeJSON)
|
||||||
|
}
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
encoder := Codecs.EncoderForVersion(yamlInfo.Serializer, tt.version)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := encoder.Encode(tt.obj, &buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tt.want, buf.String()); diff != "" {
|
||||||
|
t.Errorf("unexpected encoded configuration:\n%s", diff)
|
||||||
|
}
|
||||||
|
encoder = Codecs.EncoderForVersion(jsonInfo.Serializer, tt.version)
|
||||||
|
buf = bytes.Buffer{}
|
||||||
|
if err := encoder.Encode(tt.obj, &buf); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
out, err := yaml.JSONToYAML(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tt.want, string(out)); diff != "" {
|
||||||
|
t.Errorf("unexpected encoded configuration:\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/config/v1alpha1:go_default_library",
|
"//staging/src/k8s.io/component-base/config/v1alpha1:go_default_library",
|
||||||
"//staging/src/k8s.io/kube-scheduler/config/v1:go_default_library",
|
"//staging/src/k8s.io/kube-scheduler/config/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/kube-scheduler/config/v1alpha2:go_default_library",
|
"//staging/src/k8s.io/kube-scheduler/config/v1alpha2:go_default_library",
|
||||||
|
@ -17,20 +17,93 @@ limitations under the License.
|
|||||||
package v1alpha2
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
"fmt"
|
||||||
v1alpha2 "k8s.io/kube-scheduler/config/v1alpha2"
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/conversion"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/kube-scheduler/config/v1alpha2"
|
||||||
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
"k8s.io/kubernetes/pkg/scheduler/apis/config"
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// pluginArgConversionScheme is a scheme with internal and v1alpha2 registered,
|
||||||
|
// used for defaulting/converting typed PluginConfig Args.
|
||||||
|
// Access via getPluginArgConversionScheme()
|
||||||
|
pluginArgConversionScheme *runtime.Scheme
|
||||||
|
initPluginArgConversionScheme sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPluginArgConversionScheme() *runtime.Scheme {
|
||||||
|
initPluginArgConversionScheme.Do(func() {
|
||||||
|
// set up the scheme used for plugin arg conversion
|
||||||
|
pluginArgConversionScheme = runtime.NewScheme()
|
||||||
|
utilruntime.Must(AddToScheme(pluginArgConversionScheme))
|
||||||
|
utilruntime.Must(config.AddToScheme(pluginArgConversionScheme))
|
||||||
|
})
|
||||||
|
return pluginArgConversionScheme
|
||||||
|
}
|
||||||
|
|
||||||
func Convert_v1alpha2_KubeSchedulerConfiguration_To_config_KubeSchedulerConfiguration(in *v1alpha2.KubeSchedulerConfiguration, out *config.KubeSchedulerConfiguration, s conversion.Scope) error {
|
func Convert_v1alpha2_KubeSchedulerConfiguration_To_config_KubeSchedulerConfiguration(in *v1alpha2.KubeSchedulerConfiguration, out *config.KubeSchedulerConfiguration, s conversion.Scope) error {
|
||||||
if err := autoConvert_v1alpha2_KubeSchedulerConfiguration_To_config_KubeSchedulerConfiguration(in, out, s); err != nil {
|
if err := autoConvert_v1alpha2_KubeSchedulerConfiguration_To_config_KubeSchedulerConfiguration(in, out, s); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
out.AlgorithmSource.Provider = pointer.StringPtr(v1alpha2.SchedulerDefaultProviderName)
|
out.AlgorithmSource.Provider = pointer.StringPtr(v1alpha2.SchedulerDefaultProviderName)
|
||||||
|
return convertToInternalPluginConfigArgs(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToInternalPluginConfigArgs converts PluginConfig#Args into internal
|
||||||
|
// types using a scheme, after applying defaults.
|
||||||
|
func convertToInternalPluginConfigArgs(out *config.KubeSchedulerConfiguration) error {
|
||||||
|
scheme := getPluginArgConversionScheme()
|
||||||
|
for i := range out.Profiles {
|
||||||
|
for j := range out.Profiles[i].PluginConfig {
|
||||||
|
args := out.Profiles[i].PluginConfig[j].Args
|
||||||
|
if args == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, isUnknown := args.(*runtime.Unknown); isUnknown {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
scheme.Default(args)
|
||||||
|
internalArgs, err := scheme.ConvertToVersion(args, config.SchemeGroupVersion)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("converting .Profiles[%d].PluginConfig[%d].Args into internal type: %w", i, j, err)
|
||||||
|
}
|
||||||
|
out.Profiles[i].PluginConfig[j].Args = internalArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Convert_config_KubeSchedulerConfiguration_To_v1alpha2_KubeSchedulerConfiguration(in *config.KubeSchedulerConfiguration, out *v1alpha2.KubeSchedulerConfiguration, s conversion.Scope) error {
|
func Convert_config_KubeSchedulerConfiguration_To_v1alpha2_KubeSchedulerConfiguration(in *config.KubeSchedulerConfiguration, out *v1alpha2.KubeSchedulerConfiguration, s conversion.Scope) error {
|
||||||
return autoConvert_config_KubeSchedulerConfiguration_To_v1alpha2_KubeSchedulerConfiguration(in, out, s)
|
if err := autoConvert_config_KubeSchedulerConfiguration_To_v1alpha2_KubeSchedulerConfiguration(in, out, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return convertToExternalPluginConfigArgs(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertToExternalPluginConfigArgs converts PluginConfig#Args into
|
||||||
|
// external (versioned) types using a scheme.
|
||||||
|
func convertToExternalPluginConfigArgs(out *v1alpha2.KubeSchedulerConfiguration) error {
|
||||||
|
scheme := getPluginArgConversionScheme()
|
||||||
|
for i := range out.Profiles {
|
||||||
|
for j := range out.Profiles[i].PluginConfig {
|
||||||
|
args := out.Profiles[i].PluginConfig[j].Args
|
||||||
|
if args.Object == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, isUnknown := args.Object.(*runtime.Unknown); isUnknown {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
externalArgs, err := scheme.ConvertToVersion(args.Object, SchemeGroupVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out.Profiles[i].PluginConfig[j].Args.Object = externalArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/component-base/config/v1alpha1:go_default_library",
|
"//staging/src/k8s.io/component-base/config/v1alpha1:go_default_library",
|
||||||
"//staging/src/k8s.io/kube-scheduler/config/v1:go_default_library",
|
"//staging/src/k8s.io/kube-scheduler/config/v1:go_default_library",
|
||||||
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,10 +17,14 @@ limitations under the License.
|
|||||||
package v1alpha2
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1"
|
componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1"
|
||||||
v1 "k8s.io/kube-scheduler/config/v1"
|
v1 "k8s.io/kube-scheduler/config/v1"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,17 +77,17 @@ type KubeSchedulerConfiguration struct {
|
|||||||
// Duration to wait for a binding operation to complete before timing out
|
// Duration to wait for a binding operation to complete before timing out
|
||||||
// Value must be non-negative integer. The value zero indicates no waiting.
|
// Value must be non-negative integer. The value zero indicates no waiting.
|
||||||
// If this value is nil, the default value will be used.
|
// If this value is nil, the default value will be used.
|
||||||
BindTimeoutSeconds *int64 `json:"bindTimeoutSeconds"`
|
BindTimeoutSeconds *int64 `json:"bindTimeoutSeconds,omitempty"`
|
||||||
|
|
||||||
// PodInitialBackoffSeconds is the initial backoff for unschedulable pods.
|
// PodInitialBackoffSeconds is the initial backoff for unschedulable pods.
|
||||||
// If specified, it must be greater than 0. If this value is null, the default value (1s)
|
// If specified, it must be greater than 0. If this value is null, the default value (1s)
|
||||||
// will be used.
|
// will be used.
|
||||||
PodInitialBackoffSeconds *int64 `json:"podInitialBackoffSeconds"`
|
PodInitialBackoffSeconds *int64 `json:"podInitialBackoffSeconds,omitempty"`
|
||||||
|
|
||||||
// PodMaxBackoffSeconds is the max backoff for unschedulable pods.
|
// PodMaxBackoffSeconds is the max backoff for unschedulable pods.
|
||||||
// If specified, it must be greater than podInitialBackoffSeconds. If this value is null,
|
// If specified, it must be greater than podInitialBackoffSeconds. If this value is null,
|
||||||
// the default value (10s) will be used.
|
// the default value (10s) will be used.
|
||||||
PodMaxBackoffSeconds *int64 `json:"podMaxBackoffSeconds"`
|
PodMaxBackoffSeconds *int64 `json:"podMaxBackoffSeconds,omitempty"`
|
||||||
|
|
||||||
// Profiles are scheduling profiles that kube-scheduler supports. Pods can
|
// Profiles are scheduling profiles that kube-scheduler supports. Pods can
|
||||||
// choose to be scheduled under a particular profile by setting its associated
|
// choose to be scheduled under a particular profile by setting its associated
|
||||||
@ -91,12 +95,40 @@ type KubeSchedulerConfiguration struct {
|
|||||||
// with the "default-scheduler" profile, if present here.
|
// with the "default-scheduler" profile, if present here.
|
||||||
// +listType=map
|
// +listType=map
|
||||||
// +listMapKey=schedulerName
|
// +listMapKey=schedulerName
|
||||||
Profiles []KubeSchedulerProfile `json:"profiles"`
|
Profiles []KubeSchedulerProfile `json:"profiles,omitempty"`
|
||||||
|
|
||||||
// Extenders are the list of scheduler extenders, each holding the values of how to communicate
|
// Extenders are the list of scheduler extenders, each holding the values of how to communicate
|
||||||
// with the extender. These extenders are shared by all scheduler profiles.
|
// with the extender. These extenders are shared by all scheduler profiles.
|
||||||
// +listType=set
|
// +listType=set
|
||||||
Extenders []v1.Extender `json:"extenders"`
|
Extenders []v1.Extender `json:"extenders,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeNestedObjects decodes plugin args for known types.
|
||||||
|
func (c *KubeSchedulerConfiguration) DecodeNestedObjects(d runtime.Decoder) error {
|
||||||
|
for i := range c.Profiles {
|
||||||
|
prof := &c.Profiles[i]
|
||||||
|
for j := range prof.PluginConfig {
|
||||||
|
err := prof.PluginConfig[j].decodeNestedObjects(d)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decoding .profiles[%d].pluginConfig[%d]: %w", i, j, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeNestedObjects encodes plugin args.
|
||||||
|
func (c *KubeSchedulerConfiguration) EncodeNestedObjects(e runtime.Encoder) error {
|
||||||
|
for i := range c.Profiles {
|
||||||
|
prof := &c.Profiles[i]
|
||||||
|
for j := range prof.PluginConfig {
|
||||||
|
err := prof.PluginConfig[j].encodeNestedObjects(e)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encoding .profiles[%d].pluginConfig[%d]: %w", i, j, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// KubeSchedulerProfile is a scheduling profile.
|
// KubeSchedulerProfile is a scheduling profile.
|
||||||
@ -202,3 +234,41 @@ type PluginConfig struct {
|
|||||||
// Args defines the arguments passed to the plugins at the time of initialization. Args can have arbitrary structure.
|
// Args defines the arguments passed to the plugins at the time of initialization. Args can have arbitrary structure.
|
||||||
Args runtime.RawExtension `json:"args,omitempty"`
|
Args runtime.RawExtension `json:"args,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *PluginConfig) decodeNestedObjects(d runtime.Decoder) error {
|
||||||
|
gvk := SchemeGroupVersion.WithKind(c.Name + "Args")
|
||||||
|
// dry-run to detect and skip out-of-tree plugin args.
|
||||||
|
if _, _, err := d.Decode(nil, &gvk, nil); runtime.IsNotRegisteredError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, parsedGvk, err := d.Decode(c.Args.Raw, &gvk, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("decoding args for plugin %s: %w", c.Name, err)
|
||||||
|
}
|
||||||
|
if parsedGvk.GroupKind() != gvk.GroupKind() {
|
||||||
|
return fmt.Errorf("args for plugin %s were not of type %s, got %s", c.Name, gvk.GroupKind(), parsedGvk.GroupKind())
|
||||||
|
}
|
||||||
|
c.Args.Object = obj
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PluginConfig) encodeNestedObjects(e runtime.Encoder) error {
|
||||||
|
if c.Args.Object == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := e.Encode(c.Args.Object, &buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// The <e> encoder might be a YAML encoder, but the parent encoder expects
|
||||||
|
// JSON output, so we convert YAML back to JSON.
|
||||||
|
// This is a no-op if <e> produces JSON.
|
||||||
|
json, err := yaml.YAMLToJSON(buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.Args.Raw = json
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||||||
package v1alpha2
|
package v1alpha2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
gojson "encoding/json"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
@ -95,7 +97,7 @@ type RequestedToCapacityRatioArgs struct {
|
|||||||
Resources []ResourceSpec `json:"resources,omitempty"`
|
Resources []ResourceSpec `json:"resources,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add JSON tags and backward compatible conversion in v1beta1.
|
// TODO add JSON tags and remove custom unmarshalling in v1beta1.
|
||||||
// UtilizationShapePoint and ResourceSpec fields are not annotated with JSON tags in v1alpha2
|
// UtilizationShapePoint and ResourceSpec fields are not annotated with JSON tags in v1alpha2
|
||||||
// to maintain backward compatibility with the args shipped with v1.18.
|
// to maintain backward compatibility with the args shipped with v1.18.
|
||||||
// See https://github.com/kubernetes/kubernetes/pull/88585#discussion_r405021905
|
// See https://github.com/kubernetes/kubernetes/pull/88585#discussion_r405021905
|
||||||
@ -108,6 +110,13 @@ type UtilizationShapePoint struct {
|
|||||||
Score int32
|
Score int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON provides case insensitive unmarshalling for the type.
|
||||||
|
// TODO remove when copying to v1beta1.
|
||||||
|
func (t *UtilizationShapePoint) UnmarshalJSON(data []byte) error {
|
||||||
|
type internal *UtilizationShapePoint
|
||||||
|
return gojson.Unmarshal(data, internal(t))
|
||||||
|
}
|
||||||
|
|
||||||
// ResourceSpec represents single resource and weight for bin packing of priority RequestedToCapacityRatioArguments.
|
// ResourceSpec represents single resource and weight for bin packing of priority RequestedToCapacityRatioArguments.
|
||||||
type ResourceSpec struct {
|
type ResourceSpec struct {
|
||||||
// Name of the resource to be managed by RequestedToCapacityRatio function.
|
// Name of the resource to be managed by RequestedToCapacityRatio function.
|
||||||
@ -116,6 +125,13 @@ type ResourceSpec struct {
|
|||||||
Weight int64
|
Weight int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON provides case insensitive unmarshalling for the type.
|
||||||
|
// TODO remove when copying to v1beta1.
|
||||||
|
func (t *ResourceSpec) UnmarshalJSON(data []byte) error {
|
||||||
|
type internal *ResourceSpec
|
||||||
|
return gojson.Unmarshal(data, internal(t))
|
||||||
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
// ServiceAffinityArgs holds arguments used to configure the ServiceAffinity plugin.
|
// ServiceAffinityArgs holds arguments used to configure the ServiceAffinity plugin.
|
||||||
|
@ -9,6 +9,7 @@ require (
|
|||||||
k8s.io/api v0.0.0
|
k8s.io/api v0.0.0
|
||||||
k8s.io/apimachinery v0.0.0
|
k8s.io/apimachinery v0.0.0
|
||||||
k8s.io/component-base v0.0.0
|
k8s.io/component-base v0.0.0
|
||||||
|
sigs.k8s.io/yaml v1.2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
|
Loading…
Reference in New Issue
Block a user