...

Source file src/k8s.io/kubernetes/pkg/registry/core/node/strategy.go

Documentation: k8s.io/kubernetes/pkg/registry/core/node

     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  }
   298  

View as plain text