(ut) add tests for bad filed values checking; refactor some funcs

This commit is contained in:
Paco Xu 2022-03-07 18:17:28 +08:00
parent 3fbfd7eb1b
commit db147b7d67
11 changed files with 445 additions and 96 deletions

View File

@ -41,7 +41,6 @@
"k8s.io/api/storage/v1beta1": "storagev1beta1",
"k8s.io/apimachinery/pkg/api/errors": "apierrors",
"k8s.io/component-helpers/node/util": "nodeutil",
"k8s.io/kubernetes/pkg/api/node": "nodeapi",
"k8s.io/kubernetes/pkg/controller/util/node": "controllerutil",
"k8s.io/kubelet/apis/stats/v1alpha1": "kubeletstatsv1alpha1",
"k8s.io/kubernetes/pkg/controller/apis/config/v1alpha1": "controllerconfigv1alpha1",

View File

@ -14,17 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package persistentvolume
package node
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/node"
)
var DeprecatedNodeLabels = map[string]string{
var deprecatedNodeLabels = map[string]string{
`beta.kubernetes.io/arch`: `deprecated since v1.14; use "kubernetes.io/arch" instead`,
`beta.kubernetes.io/os`: `deprecated since v1.14; use "kubernetes.io/os" instead`,
`failure-domain.beta.kubernetes.io/region`: `deprecated since v1.17; use "topology.kubernetes.io/region" instead`,
@ -32,13 +33,20 @@ var DeprecatedNodeLabels = map[string]string{
`beta.kubernetes.io/instance-type`: `deprecated since v1.17; use "node.kubernetes.io/instance-type" instead`,
}
func GetWarningsForRuntimeClass(ctx context.Context, rc *node.RuntimeClass) []string {
// GetLabelDeprecatedMessage returns the message for the deprecated label
// and a bool indicating if the label is deprecated.
func GetLabelDeprecatedMessage(key string) (string, bool) {
msg, ok := deprecatedNodeLabels[key]
return msg, ok
}
func GetWarningsForRuntimeClass(rc *node.RuntimeClass) []string {
var warnings []string
if rc != nil {
if rc != nil && rc.Scheduling != nil && rc.Scheduling.NodeSelector != nil {
// use of deprecated node labels in scheduling's node affinity
for key, _ := range rc.Scheduling.NodeSelector {
if msg, deprecated := DeprecatedNodeLabels[key]; deprecated {
for key := range rc.Scheduling.NodeSelector {
if msg, deprecated := GetLabelDeprecatedMessage(key); deprecated {
warnings = append(warnings, fmt.Sprintf("%s: %s", field.NewPath("scheduling", "nodeSelector"), msg))
}
}
@ -46,3 +54,55 @@ func GetWarningsForRuntimeClass(ctx context.Context, rc *node.RuntimeClass) []st
return warnings
}
// WarningsForNodeSelector tests if any of the node selector requirements in the template is deprecated.
// If there are deprecated node selector requirements in either match expressions or match labels, a warning is returned.
func WarningsForNodeSelector(nodeSelector *metav1.LabelSelector, fieldPath *field.Path) []string {
if nodeSelector == nil {
return nil
}
var warnings []string
// use of deprecated node labels in matchLabelExpressions
for i, expression := range nodeSelector.MatchExpressions {
if msg, deprecated := GetLabelDeprecatedMessage(expression.Key); deprecated {
warnings = append(
warnings,
fmt.Sprintf(
"%s: %s is %s",
fieldPath.Child("matchExpressions").Index(i).Child("key"),
expression.Key,
msg,
),
)
}
}
// use of deprecated node labels in matchLabels
for label := range nodeSelector.MatchLabels {
if msg, deprecated := GetLabelDeprecatedMessage(label); deprecated {
warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("matchLabels").Child(label), msg))
}
}
return warnings
}
// WarningsForNodeSelectorTerm checks match expressions of node selector term
func WarningsForNodeSelectorTerm(nodeSelectorTerm api.NodeSelectorTerm, fieldPath *field.Path) []string {
var warnings []string
// use of deprecated node labels in matchLabelExpressions
for i, expression := range nodeSelectorTerm.MatchExpressions {
if msg, deprecated := GetLabelDeprecatedMessage(expression.Key); deprecated {
warnings = append(
warnings,
fmt.Sprintf(
"%s: %s is %s",
fieldPath.Child("matchExpressions").Index(i).Child("key"),
expression.Key,
msg,
),
)
}
}
return warnings
}

81
pkg/api/node/util_test.go Normal file
View File

@ -0,0 +1,81 @@
/*
Copyright 2022 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 node
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/apis/node"
)
func TestWarnings(t *testing.T) {
testcases := []struct {
name string
template *node.RuntimeClass
expected []string
}{
{
name: "null",
template: nil,
expected: nil,
},
{
name: "no warning",
template: &node.RuntimeClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
expected: nil,
},
{
name: "warning",
template: &node.RuntimeClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Scheduling: &node.Scheduling{
NodeSelector: map[string]string{
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
},
},
},
expected: []string{
`scheduling.nodeSelector: deprecated since v1.14; use "kubernetes.io/arch" instead`,
`scheduling.nodeSelector: deprecated since v1.14; use "kubernetes.io/os" instead`,
},
},
}
for _, tc := range testcases {
t.Run("podspec_"+tc.name, func(t *testing.T) {
actual := sets.NewString(GetWarningsForRuntimeClass(tc.template)...)
expected := sets.NewString(tc.expected...)
for _, missing := range expected.Difference(actual).List() {
t.Errorf("missing: %s", missing)
}
for _, extra := range actual.Difference(expected).List() {
t.Errorf("extra: %s", extra)
}
})
}
}

View File

@ -18,39 +18,50 @@ package persistentvolume
import (
"context"
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
nodeapi "k8s.io/kubernetes/pkg/api/node"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
// DropDisabledFields removes disabled fields from the pv spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pv spec.
func DropDisabledFields(pvSpec *api.PersistentVolumeSpec, oldPVSpec *api.PersistentVolumeSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.CSINodeExpandSecret) && !hasNodeExpansionSecrets(oldPVSpec) {
if pvSpec.CSI != nil {
pvSpec.CSI.NodeExpandSecretRef = nil
}
}
}
func hasNodeExpansionSecrets(oldPVSpec *api.PersistentVolumeSpec) bool {
if oldPVSpec == nil || oldPVSpec.CSI == nil {
return false
}
if oldPVSpec.CSI.NodeExpandSecretRef != nil {
return true
}
return false
}
func GetWarningsForPersistentVolume(ctx context.Context, pv *api.PersistentVolume) []string {
if pv == nil {
return nil
}
return warningsForPersistentVolumeSpecAndMeta(nil, &pv.Spec, &pv.ObjectMeta)
return warningsForPersistentVolumeSpecAndMeta(nil, &pv.Spec)
}
func warningsForPersistentVolumeSpecAndMeta(fieldPath *field.Path, pvSpec *api.PersistentVolumeSpec, meta *metav1.ObjectMeta) []string {
func warningsForPersistentVolumeSpecAndMeta(fieldPath *field.Path, pvSpec *api.PersistentVolumeSpec) []string {
var warnings []string
// use of deprecated node labels in node affinity
for i, k := range pvSpec.NodeAffinity.Required.NodeSelectorTerms {
expressions := k.MatchExpressions
for j, e := range expressions {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[e.Key]; deprecated {
warnings = append(
warnings,
fmt.Sprintf(
"%s: %s is %s",
fieldPath.Child("spec", "NodeAffinity").Child("Required").Child("NodeSelectorTerms").Index(i).Child("MatchExpressions").Index(j).Child("key"),
e.Key,
msg,
),
)
}
if pvSpec.NodeAffinity != nil && pvSpec.NodeAffinity.Required != nil {
termFldPath := fieldPath.Child("spec", "nodeAffinity", "required", "nodeSelectorTerms")
// use of deprecated node labels in node affinity
for i, term := range pvSpec.NodeAffinity.Required.NodeSelectorTerms {
warnings = append(warnings, nodeapi.WarningsForNodeSelectorTerm(term, termFldPath.Index(i))...)
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2018 The Kubernetes Authors.
Copyright 2022 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.
@ -17,11 +17,14 @@ limitations under the License.
package persistentvolume
import (
"context"
"reflect"
"testing"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
api "k8s.io/kubernetes/pkg/apis/core"
@ -108,3 +111,74 @@ func specWithCSISecrets(secret *api.SecretReference) *api.PersistentVolumeSpec {
}
return pvSpec
}
func TestWarnings(t *testing.T) {
testcases := []struct {
name string
template *api.PersistentVolume
expected []string
}{
{
name: "null",
template: nil,
expected: nil,
},
{
name: "no warning",
template: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Status: api.PersistentVolumeStatus{
Phase: api.VolumeBound,
},
},
expected: nil,
},
{
name: "warning",
template: &api.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: api.PersistentVolumeSpec{
NodeAffinity: &api.VolumeNodeAffinity{
Required: &api.NodeSelector{
NodeSelectorTerms: []api.NodeSelectorTerm{
{
MatchExpressions: []api.NodeSelectorRequirement{
{
Key: "beta.kubernetes.io/os",
Operator: "Equal",
Values: []string{"windows"},
},
},
},
},
},
},
},
Status: api.PersistentVolumeStatus{
Phase: api.VolumeBound,
},
},
expected: []string{
`spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
},
},
}
for _, tc := range testcases {
t.Run("podspec_"+tc.name, func(t *testing.T) {
actual := sets.NewString(GetWarningsForPersistentVolume(context.TODO(), tc.template)...)
expected := sets.NewString(tc.expected...)
for _, missing := range expected.Difference(actual).List() {
t.Errorf("missing: %s", missing)
}
for _, extra := range actual.Difference(expected).List() {
t.Errorf("extra: %s", extra)
}
})
}
}

View File

@ -85,52 +85,25 @@ func warningsForPodSpecAndMeta(fieldPath *field.Path, podSpec *api.PodSpec, meta
// use of deprecated node labels in selectors/affinity/topology
for k := range podSpec.NodeSelector {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[k]; deprecated {
if msg, deprecated := nodeapi.GetLabelDeprecatedMessage(k); deprecated {
warnings = append(warnings, fmt.Sprintf("%s: %s", fieldPath.Child("spec", "nodeSelector").Key(k), msg))
}
}
if podSpec.Affinity != nil && podSpec.Affinity.NodeAffinity != nil {
n := podSpec.Affinity.NodeAffinity
if n.RequiredDuringSchedulingIgnoredDuringExecution != nil {
for i, t := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
for j, e := range t.MatchExpressions {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[e.Key]; deprecated {
warnings = append(
warnings,
fmt.Sprintf(
"%s: %s is %s",
fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms").Index(i).
Child("matchExpressions").Index(j).
Child("key"),
e.Key,
msg,
),
)
}
}
termFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "requiredDuringSchedulingIgnoredDuringExecution", "nodeSelectorTerms")
for i, term := range n.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms {
warnings = append(warnings, nodeapi.WarningsForNodeSelectorTerm(term, termFldPath.Index(i))...)
}
}
for i, t := range n.PreferredDuringSchedulingIgnoredDuringExecution {
for j, e := range t.Preference.MatchExpressions {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[e.Key]; deprecated {
warnings = append(
warnings,
fmt.Sprintf(
"%s: %s is %s",
fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution").Index(i).
Child("preference").
Child("matchExpressions").Index(j).
Child("key"),
e.Key,
msg,
),
)
}
}
preferredFldPath := fieldPath.Child("spec", "affinity", "nodeAffinity", "preferredDuringSchedulingIgnoredDuringExecution")
for i, term := range n.PreferredDuringSchedulingIgnoredDuringExecution {
warnings = append(warnings, nodeapi.WarningsForNodeSelectorTerm(term.Preference, preferredFldPath.Index(i).Child("preference"))...)
}
}
for i, t := range podSpec.TopologySpreadConstraints {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[t.TopologyKey]; deprecated {
if msg, deprecated := nodeapi.GetLabelDeprecatedMessage(t.TopologyKey); deprecated {
warnings = append(warnings, fmt.Sprintf(
"%s: %s is %s",
fieldPath.Child("spec", "topologySpreadConstraints").Index(i).Child("topologyKey"),

View File

@ -28,12 +28,12 @@ import (
func GetWarningsForStorageClass(ctx context.Context, sc *storage.StorageClass) []string {
var warnings []string
if sc != nil {
if sc != nil && sc.AllowedTopologies != nil {
// use of deprecated node labels in allowedTopologies's matchLabelExpressions
for i, topo := range sc.AllowedTopologies {
for j, expression := range topo.MatchLabelExpressions {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[expression.Key]; deprecated {
warnings = append(warnings, fmt.Sprintf("%s: %s", field.NewPath("allowedTopologies").Index(i).Child("matchLabelExpressions").Index(j), msg))
if msg, deprecated := nodeapi.GetLabelDeprecatedMessage(expression.Key); deprecated {
warnings = append(warnings, fmt.Sprintf("%s: %s", field.NewPath("allowedTopologies").Index(i).Child("matchLabelExpressions").Index(j).Child("key"), msg))
}
}
}
@ -43,31 +43,8 @@ func GetWarningsForStorageClass(ctx context.Context, sc *storage.StorageClass) [
}
func GetWarningsForCSIStorageCapacity(ctx context.Context, csc *storage.CSIStorageCapacity) []string {
var warnings []string
if csc != nil {
// use of deprecated node labels in allowedTopologies's matchLabelExpressions
for i, expression := range csc.NodeTopology.MatchExpressions {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[expression.Key]; deprecated {
warnings = append(
warnings,
fmt.Sprintf(
"%s: %s is %s",
field.NewPath("nodeTopology").Child("matchExpressions").Index(i),
expression.Key,
msg,
),
)
}
}
// use of deprecated node labels in allowedTopologies's matchLabels
for label, _ := range csc.NodeTopology.MatchLabels {
if msg, deprecated := nodeapi.DeprecatedNodeLabels[label]; deprecated {
warnings = append(warnings, fmt.Sprintf("%s: %s", field.NewPath("nodeTopology").Child("matchLabels").Child(label), msg))
}
}
return nodeapi.WarningsForNodeSelector(csc.NodeTopology, field.NewPath("nodeTopology"))
}
return warnings
return nil
}

View File

@ -0,0 +1,172 @@
/*
Copyright 2022 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 storage
import (
"context"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/storage"
)
func TestStorageClassWarnings(t *testing.T) {
testcases := []struct {
name string
template *storage.StorageClass
expected []string
}{
{
name: "null",
template: nil,
expected: nil,
},
{
name: "no warning",
template: &storage.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
expected: nil,
},
{
name: "warning",
template: &storage.StorageClass{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
AllowedTopologies: []core.TopologySelectorTerm{
{
MatchLabelExpressions: []core.TopologySelectorLabelRequirement{
{
Key: "beta.kubernetes.io/arch",
Values: []string{"amd64"},
},
{
Key: "beta.kubernetes.io/os",
Values: []string{"linux"},
},
},
},
},
},
expected: []string{
`allowedTopologies[0].matchLabelExpressions[0].key: deprecated since v1.14; use "kubernetes.io/arch" instead`,
`allowedTopologies[0].matchLabelExpressions[1].key: deprecated since v1.14; use "kubernetes.io/os" instead`,
},
},
}
for _, tc := range testcases {
t.Run("podspec_"+tc.name, func(t *testing.T) {
actual := sets.NewString(GetWarningsForStorageClass(context.TODO(), tc.template)...)
expected := sets.NewString(tc.expected...)
for _, missing := range expected.Difference(actual).List() {
t.Errorf("missing: %s", missing)
}
for _, extra := range actual.Difference(expected).List() {
t.Errorf("extra: %s", extra)
}
})
}
}
func TestCSIStorageCapacityWarnings(t *testing.T) {
testcases := []struct {
name string
template *storage.CSIStorageCapacity
expected []string
}{
{
name: "null",
template: nil,
expected: nil,
},
{
name: "no warning",
template: &storage.CSIStorageCapacity{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
},
expected: nil,
},
{
name: "MatchLabels warning",
template: &storage.CSIStorageCapacity{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
NodeTopology: &metav1.LabelSelector{
MatchLabels: map[string]string{
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/os": "linux",
},
},
},
expected: []string{
`nodeTopology.matchLabels.beta.kubernetes.io/arch: deprecated since v1.14; use "kubernetes.io/arch" instead`,
`nodeTopology.matchLabels.beta.kubernetes.io/os: deprecated since v1.14; use "kubernetes.io/os" instead`,
},
},
{
name: "MatchExpressions warning",
template: &storage.CSIStorageCapacity{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
NodeTopology: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "beta.kubernetes.io/arch",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"amd64"},
},
{
Key: "beta.kubernetes.io/os",
Operator: metav1.LabelSelectorOpIn,
Values: []string{"linux"},
},
},
},
},
expected: []string{
`nodeTopology.matchExpressions[0].key: beta.kubernetes.io/arch is deprecated since v1.14; use "kubernetes.io/arch" instead`,
`nodeTopology.matchExpressions[1].key: beta.kubernetes.io/os is deprecated since v1.14; use "kubernetes.io/os" instead`,
},
},
}
for _, tc := range testcases {
t.Run("podspec_"+tc.name, func(t *testing.T) {
actual := sets.NewString(GetWarningsForCSIStorageCapacity(context.TODO(), tc.template)...)
expected := sets.NewString(tc.expected...)
for _, missing := range expected.Difference(actual).List() {
t.Errorf("missing: %s", missing)
}
for _, extra := range actual.Difference(expected).List() {
t.Errorf("extra: %s", extra)
}
})
}
}

View File

@ -75,7 +75,7 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis
// WarningsOnCreate returns warnings for the creation of the given object.
func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return nodeapi.GetWarningsForRuntimeClass(ctx, obj.(*node.RuntimeClass))
return nodeapi.GetWarningsForRuntimeClass(obj.(*node.RuntimeClass))
}
// Canonicalize normalizes the object after validation.
@ -92,7 +92,7 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie
// WarningsOnUpdate returns warnings for the given update.
func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nodeapi.GetWarningsForRuntimeClass(ctx, obj.(*node.RuntimeClass))
return nodeapi.GetWarningsForRuntimeClass(obj.(*node.RuntimeClass))
}
// If AllowUnconditionalUpdate() is true and the object specified by

View File

@ -23,7 +23,7 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
storageutil2 "k8s.io/kubernetes/pkg/api/storage"
storageutil "k8s.io/kubernetes/pkg/api/storage"
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/apis/storage/validation"
)
@ -53,7 +53,7 @@ func (storageClassStrategy) Validate(ctx context.Context, obj runtime.Object) fi
// WarningsOnCreate returns warnings for the creation of the given object.
func (storageClassStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
return storageutil2.GetWarningsForStorageClass(ctx, obj.(*storage.StorageClass))
return storageutil.GetWarningsForStorageClass(ctx, obj.(*storage.StorageClass))
}
// Canonicalize normalizes the object after validation.
@ -75,7 +75,7 @@ func (storageClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime
// WarningsOnUpdate returns warnings for the given update.
func (storageClassStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return storageutil2.GetWarningsForStorageClass(ctx, obj.(*storage.StorageClass))
return storageutil.GetWarningsForStorageClass(ctx, obj.(*storage.StorageClass))
}
func (storageClassStrategy) AllowUnconditionalUpdate() bool {

View File

@ -7,6 +7,7 @@ rules:
- k8s.io/kubernetes/pkg/api/v1/resource
- k8s.io/kubernetes/pkg/api/v1/service
- k8s.io/kubernetes/pkg/api/pod
- k8s.io/kubernetes/pkg/api/node
- k8s.io/kubernetes/pkg/apis/apps
- k8s.io/kubernetes/pkg/apis/apps/validation
- k8s.io/kubernetes/pkg/apis/autoscaling
@ -23,6 +24,7 @@ rules:
- k8s.io/kubernetes/pkg/apis/core/validation
- k8s.io/kubernetes/pkg/apis/extensions
- k8s.io/kubernetes/pkg/apis/networking
- k8s.io/kubernetes/pkg/apis/node
- k8s.io/kubernetes/pkg/apis/policy
- k8s.io/kubernetes/pkg/apis/policy/validation
- k8s.io/kubernetes/pkg/apis/scheduling