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  }