Add decoding of nested scheduling plugin configs

Signed-off-by: Aldo Culquicondor <acondor@google.com>
This commit is contained in:
Aldo Culquicondor 2020-04-07 16:39:27 -04:00
parent 7814f3aaf7
commit 6b153dc920
8 changed files with 638 additions and 10 deletions

View File

@ -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(
name = "go_default_library",
@ -28,3 +28,19 @@ filegroup(
tags = ["automanaged"],
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",
],
)

View 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)
}
})
}
}

View File

@ -20,6 +20,7 @@ go_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/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/kube-scheduler/config/v1:go_default_library",
"//staging/src/k8s.io/kube-scheduler/config/v1alpha2:go_default_library",

View File

@ -17,20 +17,93 @@ limitations under the License.
package v1alpha2
import (
conversion "k8s.io/apimachinery/pkg/conversion"
v1alpha2 "k8s.io/kube-scheduler/config/v1alpha2"
"fmt"
"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/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 {
if err := autoConvert_v1alpha2_KubeSchedulerConfiguration_To_config_KubeSchedulerConfiguration(in, out, s); err != nil {
return err
}
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
}
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
}

View File

@ -19,6 +19,7 @@ go_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/kube-scheduler/config/v1:go_default_library",
"//vendor/sigs.k8s.io/yaml:go_default_library",
],
)

View File

@ -17,10 +17,14 @@ limitations under the License.
package v1alpha2
import (
"bytes"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
componentbaseconfigv1alpha1 "k8s.io/component-base/config/v1alpha1"
v1 "k8s.io/kube-scheduler/config/v1"
"sigs.k8s.io/yaml"
)
const (
@ -73,17 +77,17 @@ type KubeSchedulerConfiguration struct {
// Duration to wait for a binding operation to complete before timing out
// Value must be non-negative integer. The value zero indicates no waiting.
// 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.
// If specified, it must be greater than 0. If this value is null, the default value (1s)
// will be used.
PodInitialBackoffSeconds *int64 `json:"podInitialBackoffSeconds"`
PodInitialBackoffSeconds *int64 `json:"podInitialBackoffSeconds,omitempty"`
// PodMaxBackoffSeconds is the max backoff for unschedulable pods.
// If specified, it must be greater than podInitialBackoffSeconds. If this value is null,
// 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
// 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.
// +listType=map
// +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
// with the extender. These extenders are shared by all scheduler profiles.
// +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.
@ -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 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
}

View File

@ -17,6 +17,8 @@ limitations under the License.
package v1alpha2
import (
gojson "encoding/json"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -95,7 +97,7 @@ type RequestedToCapacityRatioArgs struct {
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
// to maintain backward compatibility with the args shipped with v1.18.
// See https://github.com/kubernetes/kubernetes/pull/88585#discussion_r405021905
@ -108,6 +110,13 @@ type UtilizationShapePoint struct {
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.
type ResourceSpec struct {
// Name of the resource to be managed by RequestedToCapacityRatio function.
@ -116,6 +125,13 @@ type ResourceSpec struct {
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
// ServiceAffinityArgs holds arguments used to configure the ServiceAffinity plugin.

View File

@ -9,6 +9,7 @@ require (
k8s.io/api v0.0.0
k8s.io/apimachinery v0.0.0
k8s.io/component-base v0.0.0
sigs.k8s.io/yaml v1.2.0
)
replace (