k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/registry/core/node/strategy.go (about) 1 /* 2 Copyright 2014 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 node 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "net/http" 24 "net/url" 25 26 "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/fields" 29 "k8s.io/apimachinery/pkg/labels" 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/types" 32 utilnet "k8s.io/apimachinery/pkg/util/net" 33 "k8s.io/apimachinery/pkg/util/validation/field" 34 "k8s.io/apiserver/pkg/registry/generic" 35 pkgstorage "k8s.io/apiserver/pkg/storage" 36 "k8s.io/apiserver/pkg/storage/names" 37 utilfeature "k8s.io/apiserver/pkg/util/feature" 38 "k8s.io/kubernetes/pkg/api/legacyscheme" 39 api "k8s.io/kubernetes/pkg/apis/core" 40 "k8s.io/kubernetes/pkg/apis/core/validation" 41 "k8s.io/kubernetes/pkg/features" 42 "k8s.io/kubernetes/pkg/kubelet/client" 43 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 44 ) 45 46 // nodeStrategy implements behavior for nodes 47 type nodeStrategy struct { 48 runtime.ObjectTyper 49 names.NameGenerator 50 } 51 52 // Nodes is the default logic that applies when creating and updating Node 53 // objects. 54 var Strategy = nodeStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} 55 56 // NamespaceScoped is false for nodes. 57 func (nodeStrategy) NamespaceScoped() bool { 58 return false 59 } 60 61 // GetResetFields returns the set of fields that get reset by the strategy 62 // and should not be modified by the user. 63 func (nodeStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { 64 fields := map[fieldpath.APIVersion]*fieldpath.Set{ 65 "v1": fieldpath.NewSet( 66 fieldpath.MakePathOrDie("status"), 67 ), 68 } 69 70 return fields 71 } 72 73 // AllowCreateOnUpdate is false for nodes. 74 func (nodeStrategy) AllowCreateOnUpdate() bool { 75 return false 76 } 77 78 // PrepareForCreate clears fields that are not allowed to be set by end users on creation. 79 func (nodeStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { 80 node := obj.(*api.Node) 81 dropDisabledFields(node, nil) 82 } 83 84 // PrepareForUpdate clears fields that are not allowed to be set by end users on update. 85 func (nodeStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { 86 newNode := obj.(*api.Node) 87 oldNode := old.(*api.Node) 88 newNode.Status = oldNode.Status 89 90 dropDisabledFields(newNode, oldNode) 91 } 92 93 func dropDisabledFields(node *api.Node, oldNode *api.Node) { 94 // Nodes allow *all* fields, including status, to be set on create. 95 // for create 96 if oldNode == nil { 97 node.Spec.ConfigSource = nil 98 node.Status.Config = nil 99 } 100 101 // for update 102 if !nodeConfigSourceInUse(oldNode) && oldNode != nil { 103 node.Spec.ConfigSource = nil 104 } 105 106 if !utilfeature.DefaultFeatureGate.Enabled(features.RecursiveReadOnlyMounts) { 107 node.Status.RuntimeHandlers = nil 108 } 109 } 110 111 // nodeConfigSourceInUse returns true if node's Spec ConfigSource is set(used) 112 func nodeConfigSourceInUse(node *api.Node) bool { 113 if node == nil { 114 return false 115 } 116 if node.Spec.ConfigSource != nil { 117 return true 118 } 119 return false 120 } 121 122 // Validate validates a new node. 123 func (nodeStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { 124 node := obj.(*api.Node) 125 return validation.ValidateNode(node) 126 } 127 128 // WarningsOnCreate returns warnings for the creation of the given object. 129 func (nodeStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { 130 return fieldIsDeprecatedWarnings(obj) 131 } 132 133 // Canonicalize normalizes the object after validation. 134 func (nodeStrategy) Canonicalize(obj runtime.Object) { 135 } 136 137 // ValidateUpdate is the default update validation for an end user. 138 func (nodeStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { 139 errorList := validation.ValidateNode(obj.(*api.Node)) 140 return append(errorList, validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node))...) 141 } 142 143 // WarningsOnUpdate returns warnings for the given update. 144 func (nodeStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 145 return fieldIsDeprecatedWarnings(obj) 146 } 147 148 func (nodeStrategy) AllowUnconditionalUpdate() bool { 149 return true 150 } 151 152 type nodeStatusStrategy struct { 153 nodeStrategy 154 } 155 156 var StatusStrategy = nodeStatusStrategy{Strategy} 157 158 // GetResetFields returns the set of fields that get reset by the strategy 159 // and should not be modified by the user. 160 func (nodeStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { 161 fields := map[fieldpath.APIVersion]*fieldpath.Set{ 162 "v1": fieldpath.NewSet( 163 fieldpath.MakePathOrDie("spec"), 164 ), 165 } 166 167 return fields 168 } 169 170 func (nodeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { 171 newNode := obj.(*api.Node) 172 oldNode := old.(*api.Node) 173 newNode.Spec = oldNode.Spec 174 175 if !nodeStatusConfigInUse(oldNode) { 176 newNode.Status.Config = nil 177 } 178 } 179 180 // nodeStatusConfigInUse returns true if node's Status Config is set(used) 181 func nodeStatusConfigInUse(node *api.Node) bool { 182 if node == nil { 183 return false 184 } 185 if node.Status.Config != nil { 186 return true 187 } 188 return false 189 } 190 191 func (nodeStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { 192 return validation.ValidateNodeUpdate(obj.(*api.Node), old.(*api.Node)) 193 } 194 195 // WarningsOnUpdate returns warnings for the given update. 196 func (nodeStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { 197 return nil 198 } 199 200 // Canonicalize normalizes the object after validation. 201 func (nodeStatusStrategy) Canonicalize(obj runtime.Object) { 202 } 203 204 // ResourceGetter is an interface for retrieving resources by ResourceLocation. 205 type ResourceGetter interface { 206 Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error) 207 } 208 209 // NodeToSelectableFields returns a field set that represents the object. 210 func NodeToSelectableFields(node *api.Node) fields.Set { 211 objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&node.ObjectMeta, false) 212 specificFieldsSet := fields.Set{ 213 "spec.unschedulable": fmt.Sprint(node.Spec.Unschedulable), 214 } 215 return generic.MergeFieldsSets(objectMetaFieldsSet, specificFieldsSet) 216 } 217 218 // GetAttrs returns labels and fields of a given object for filtering purposes. 219 func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { 220 nodeObj, ok := obj.(*api.Node) 221 if !ok { 222 return nil, nil, fmt.Errorf("not a node") 223 } 224 return labels.Set(nodeObj.ObjectMeta.Labels), NodeToSelectableFields(nodeObj), nil 225 } 226 227 // MatchNode returns a generic matcher for a given label and field selector. 228 func MatchNode(label labels.Selector, field fields.Selector) pkgstorage.SelectionPredicate { 229 return pkgstorage.SelectionPredicate{ 230 Label: label, 231 Field: field, 232 GetAttrs: GetAttrs, 233 } 234 } 235 236 // ResourceLocation returns a URL and transport which one can use to send traffic for the specified node. 237 func ResourceLocation(getter ResourceGetter, connection client.ConnectionInfoGetter, proxyTransport http.RoundTripper, ctx context.Context, id string) (*url.URL, http.RoundTripper, error) { 238 schemeReq, name, portReq, valid := utilnet.SplitSchemeNamePort(id) 239 if !valid { 240 return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid node request %q", id)) 241 } 242 243 info, err := connection.GetConnectionInfo(ctx, types.NodeName(name)) 244 if err != nil { 245 return nil, nil, err 246 } 247 248 if err := isProxyableHostname(ctx, info.Hostname); err != nil { 249 return nil, nil, errors.NewBadRequest(err.Error()) 250 } 251 252 // We check if we want to get a default Kubelet's transport. It happens if either: 253 // - no port is specified in request (Kubelet's port is default) 254 // - the requested port matches the kubelet port for this node 255 if portReq == "" || portReq == info.Port { 256 return &url.URL{ 257 Scheme: info.Scheme, 258 Host: net.JoinHostPort(info.Hostname, info.Port), 259 }, 260 info.Transport, 261 nil 262 } 263 264 // Otherwise, return the requested scheme and port, and the proxy transport 265 return &url.URL{Scheme: schemeReq, Host: net.JoinHostPort(info.Hostname, portReq)}, proxyTransport, nil 266 } 267 268 func isProxyableHostname(ctx context.Context, hostname string) error { 269 resp, err := net.DefaultResolver.LookupIPAddr(ctx, hostname) 270 if err != nil { 271 return err 272 } 273 274 if len(resp) == 0 { 275 return fmt.Errorf("no addresses for hostname") 276 } 277 for _, host := range resp { 278 if !host.IP.IsGlobalUnicast() { 279 return fmt.Errorf("address not allowed") 280 } 281 } 282 283 return nil 284 } 285 286 func fieldIsDeprecatedWarnings(obj runtime.Object) []string { 287 newNode := obj.(*api.Node) 288 var warnings []string 289 if newNode.Spec.ConfigSource != nil { 290 // KEP https://github.com/kubernetes/enhancements/issues/281 291 warnings = append(warnings, "spec.configSource: the feature is removed") 292 } 293 if len(newNode.Spec.DoNotUseExternalID) > 0 { 294 warnings = append(warnings, "spec.externalID: this field is deprecated, and is unused by Kubernetes") 295 } 296 return warnings 297 }