k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/discovery/endpointslice/strategy.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package endpointslice 18 19 import ( 20 "context" 21 "fmt" 22 23 corev1 "k8s.io/api/core/v1" 24 discoveryv1beta1 "k8s.io/api/discovery/v1beta1" 25 apiequality "k8s.io/apimachinery/pkg/api/equality" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 "k8s.io/apimachinery/pkg/runtime/schema" 29 "k8s.io/apimachinery/pkg/util/sets" 30 "k8s.io/apimachinery/pkg/util/validation/field" 31 genericapirequest "k8s.io/apiserver/pkg/endpoints/request" 32 "k8s.io/apiserver/pkg/storage/names" 33 utilfeature "k8s.io/apiserver/pkg/util/feature" 34 "k8s.io/kubernetes/pkg/api/legacyscheme" 35 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 36 "k8s.io/kubernetes/pkg/apis/discovery" 37 "k8s.io/kubernetes/pkg/apis/discovery/validation" 38 "k8s.io/kubernetes/pkg/features" 39 ) 40 41 // endpointSliceStrategy implements verification logic for Replication. 42 type endpointSliceStrategy struct { 43 runtime.ObjectTyper 44 names.NameGenerator 45 } 46 47 // Strategy is the default logic that applies when creating and updating Replication EndpointSlice objects. 48 var Strategy = endpointSliceStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} 49 50 // NamespaceScoped returns true because all EndpointSlices need to be within a namespace. 51 func (endpointSliceStrategy) NamespaceScoped() bool { 52 return true 53 } 54 55 // PrepareForCreate clears the status of an EndpointSlice before creation. 56 func (endpointSliceStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { 57 endpointSlice := obj.(*discovery.EndpointSlice) 58 endpointSlice.Generation = 1 59 60 dropDisabledFieldsOnCreate(endpointSlice) 61 dropTopologyOnV1(ctx, nil, endpointSlice) 62 } 63 64 // PrepareForUpdate clears fields that are not allowed to be set by end users on update. 65 func (endpointSliceStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { 66 newEPS := obj.(*discovery.EndpointSlice) 67 oldEPS := old.(*discovery.EndpointSlice) 68 69 // Increment generation if anything other than meta changed 70 // This needs to be changed if a status attribute is added to EndpointSlice 71 ogNewMeta := newEPS.ObjectMeta 72 ogOldMeta := oldEPS.ObjectMeta 73 newEPS.ObjectMeta = metav1.ObjectMeta{} 74 oldEPS.ObjectMeta = metav1.ObjectMeta{} 75 76 if !apiequality.Semantic.DeepEqual(newEPS, oldEPS) || !apiequality.Semantic.DeepEqual(ogNewMeta.Labels, ogOldMeta.Labels) { 77 ogNewMeta.Generation = ogOldMeta.Generation + 1 78 } 79 80 newEPS.ObjectMeta = ogNewMeta 81 oldEPS.ObjectMeta = ogOldMeta 82 83 dropDisabledFieldsOnUpdate(oldEPS, newEPS) 84 dropTopologyOnV1(ctx, oldEPS, newEPS) 85 } 86 87 // Validate validates a new EndpointSlice. 88 func (endpointSliceStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { 89 endpointSlice := obj.(*discovery.EndpointSlice) 90 err := validation.ValidateEndpointSliceCreate(endpointSlice) 91 return err 92 } 93 94 // WarningsOnCreate returns warnings for the creation of the given object. 95 func (endpointSliceStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { 96 eps := obj.(*discovery.EndpointSlice) 97 if eps == nil { 98 return nil 99 } 100 var warnings []string 101 warnings = append(warnings, warnOnDeprecatedAddressType(eps.AddressType)...) 102 return warnings 103 } 104 105 // Canonicalize normalizes the object after validation. 106 func (endpointSliceStrategy) Canonicalize(obj runtime.Object) { 107 } 108 109 // AllowCreateOnUpdate is false for EndpointSlice; this means POST is needed to create one. 110 func (endpointSliceStrategy) AllowCreateOnUpdate() bool { 111 return false 112 } 113 114 // ValidateUpdate is the default update validation for an end user. 115 func (endpointSliceStrategy) ValidateUpdate(ctx context.Context, new, old runtime.Object) field.ErrorList { 116 newEPS := new.(*discovery.EndpointSlice) 117 oldEPS := old.(*discovery.EndpointSlice) 118 return validation.ValidateEndpointSliceUpdate(newEPS, oldEPS) 119 } 120 121 // WarningsOnUpdate returns warnings for the given update. 122 func (endpointSliceStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 123 return nil 124 } 125 126 // AllowUnconditionalUpdate is the default update policy for EndpointSlice objects. 127 func (endpointSliceStrategy) AllowUnconditionalUpdate() bool { 128 return true 129 } 130 131 // dropDisabledConditionsOnCreate will drop any fields that are disabled. 132 func dropDisabledFieldsOnCreate(endpointSlice *discovery.EndpointSlice) { 133 dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) 134 135 if dropHints { 136 for i := range endpointSlice.Endpoints { 137 if dropHints { 138 endpointSlice.Endpoints[i].Hints = nil 139 } 140 } 141 } 142 } 143 144 // dropDisabledFieldsOnUpdate will drop any disable fields that have not already 145 // been set on the EndpointSlice. 146 func dropDisabledFieldsOnUpdate(oldEPS, newEPS *discovery.EndpointSlice) { 147 dropHints := !utilfeature.DefaultFeatureGate.Enabled(features.TopologyAwareHints) 148 if dropHints { 149 for _, ep := range oldEPS.Endpoints { 150 if ep.Hints != nil { 151 dropHints = false 152 break 153 } 154 } 155 } 156 157 if dropHints { 158 for i := range newEPS.Endpoints { 159 if dropHints { 160 newEPS.Endpoints[i].Hints = nil 161 } 162 } 163 } 164 } 165 166 // dropTopologyOnV1 on V1 request wipes the DeprecatedTopology field and copies 167 // the NodeName value into DeprecatedTopology 168 func dropTopologyOnV1(ctx context.Context, oldEPS, newEPS *discovery.EndpointSlice) { 169 if info, ok := genericapirequest.RequestInfoFrom(ctx); ok { 170 requestGV := schema.GroupVersion{Group: info.APIGroup, Version: info.APIVersion} 171 if requestGV == discoveryv1beta1.SchemeGroupVersion { 172 return 173 } 174 175 // do not drop topology if endpoints have not been changed 176 if oldEPS != nil && apiequality.Semantic.DeepEqual(oldEPS.Endpoints, newEPS.Endpoints) { 177 return 178 } 179 180 // Only node names that exist in previous version of the EndpointSlice 181 // deprecatedTopology fields may be retained in new version of the 182 // EndpointSlice. 183 prevNodeNames := getDeprecatedTopologyNodeNames(oldEPS) 184 185 for i := range newEPS.Endpoints { 186 ep := &newEPS.Endpoints[i] 187 188 newTopologyNodeName, ok := ep.DeprecatedTopology[corev1.LabelHostname] 189 if ep.NodeName == nil && ok && prevNodeNames.Has(newTopologyNodeName) && len(apivalidation.ValidateNodeName(newTopologyNodeName, false)) == 0 { 190 // Copy the label previously used to store the node name into the nodeName field, 191 // in order to make partial updates preserve previous node info 192 ep.NodeName = &newTopologyNodeName 193 } 194 // Drop writes to this field via the v1 API as documented 195 ep.DeprecatedTopology = nil 196 } 197 } 198 } 199 200 // getDeprecatedTopologyNodeNames returns a set of node names present in 201 // deprecatedTopology fields within the provided EndpointSlice. 202 func getDeprecatedTopologyNodeNames(eps *discovery.EndpointSlice) sets.String { 203 if eps == nil { 204 return nil 205 } 206 var names sets.String 207 for _, ep := range eps.Endpoints { 208 if nodeName, ok := ep.DeprecatedTopology[corev1.LabelHostname]; ok && len(nodeName) > 0 { 209 if names == nil { 210 names = sets.NewString() 211 } 212 names.Insert(nodeName) 213 } 214 } 215 return names 216 } 217 218 // warnOnDeprecatedAddressType returns a warning for endpointslices with FQDN AddressType 219 func warnOnDeprecatedAddressType(addressType discovery.AddressType) []string { 220 if addressType == discovery.AddressTypeFQDN { 221 return []string{fmt.Sprintf("%s: FQDN endpoints are deprecated", field.NewPath("spec").Child("addressType"))} 222 } 223 return nil 224 }