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