k8s.io/kubernetes@v1.29.3/pkg/registry/core/pod/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 pod
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"net"
    23  	"net/http"
    24  	"net/url"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	apiv1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/api/errors"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/fields"
    33  	"k8s.io/apimachinery/pkg/labels"
    34  	"k8s.io/apimachinery/pkg/runtime"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	utilnet "k8s.io/apimachinery/pkg/util/net"
    37  	utilvalidation "k8s.io/apimachinery/pkg/util/validation"
    38  	"k8s.io/apimachinery/pkg/util/validation/field"
    39  	"k8s.io/apiserver/pkg/registry/generic"
    40  	"k8s.io/apiserver/pkg/storage"
    41  	"k8s.io/apiserver/pkg/storage/names"
    42  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    43  	"k8s.io/client-go/tools/cache"
    44  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    45  	podutil "k8s.io/kubernetes/pkg/api/pod"
    46  	api "k8s.io/kubernetes/pkg/apis/core"
    47  	"k8s.io/kubernetes/pkg/apis/core/helper/qos"
    48  	corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
    49  	"k8s.io/kubernetes/pkg/features"
    50  	"k8s.io/kubernetes/pkg/kubelet/client"
    51  	netutils "k8s.io/utils/net"
    52  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    53  )
    54  
    55  // podStrategy implements behavior for Pods
    56  type podStrategy struct {
    57  	runtime.ObjectTyper
    58  	names.NameGenerator
    59  }
    60  
    61  // Strategy is the default logic that applies when creating and updating Pod
    62  // objects via the REST API.
    63  var Strategy = podStrategy{legacyscheme.Scheme, names.SimpleNameGenerator}
    64  
    65  // NamespaceScoped is true for pods.
    66  func (podStrategy) NamespaceScoped() bool {
    67  	return true
    68  }
    69  
    70  // GetResetFields returns the set of fields that get reset by the strategy
    71  // and should not be modified by the user.
    72  func (podStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
    73  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
    74  		"v1": fieldpath.NewSet(
    75  			fieldpath.MakePathOrDie("status"),
    76  		),
    77  	}
    78  
    79  	return fields
    80  }
    81  
    82  // PrepareForCreate clears fields that are not allowed to be set by end users on creation.
    83  func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
    84  	pod := obj.(*api.Pod)
    85  	pod.Status = api.PodStatus{
    86  		Phase:    api.PodPending,
    87  		QOSClass: qos.GetPodQOS(pod),
    88  	}
    89  
    90  	podutil.DropDisabledPodFields(pod, nil)
    91  
    92  	applyWaitingForSchedulingGatesCondition(pod)
    93  	mutatePodAffinity(pod)
    94  }
    95  
    96  // PrepareForUpdate clears fields that are not allowed to be set by end users on update.
    97  func (podStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
    98  	newPod := obj.(*api.Pod)
    99  	oldPod := old.(*api.Pod)
   100  	newPod.Status = oldPod.Status
   101  
   102  	if utilfeature.DefaultFeatureGate.Enabled(features.InPlacePodVerticalScaling) {
   103  		// With support for in-place pod resizing, container resources are now mutable.
   104  		// If container resources are updated with new resource requests values, a pod resize is
   105  		// desired. The status of this request is reflected by setting Resize field to "Proposed"
   106  		// as a signal to the caller that the request is being considered.
   107  		podutil.MarkPodProposedForResize(oldPod, newPod)
   108  	}
   109  
   110  	podutil.DropDisabledPodFields(newPod, oldPod)
   111  }
   112  
   113  // Validate validates a new pod.
   114  func (podStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
   115  	pod := obj.(*api.Pod)
   116  	opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, nil, &pod.ObjectMeta, nil)
   117  	opts.ResourceIsPod = true
   118  	return corevalidation.ValidatePodCreate(pod, opts)
   119  }
   120  
   121  // WarningsOnCreate returns warnings for the creation of the given object.
   122  func (podStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
   123  	newPod := obj.(*api.Pod)
   124  	var warnings []string
   125  	if msgs := utilvalidation.IsDNS1123Label(newPod.Name); len(msgs) != 0 {
   126  		warnings = append(warnings, fmt.Sprintf("metadata.name: this is used in the Pod's hostname, which can result in surprising behavior; a DNS label is recommended: %v", msgs))
   127  	}
   128  	warnings = append(warnings, podutil.GetWarningsForPod(ctx, newPod, nil)...)
   129  	return warnings
   130  }
   131  
   132  // Canonicalize normalizes the object after validation.
   133  func (podStrategy) Canonicalize(obj runtime.Object) {
   134  }
   135  
   136  // AllowCreateOnUpdate is false for pods.
   137  func (podStrategy) AllowCreateOnUpdate() bool {
   138  	return false
   139  }
   140  
   141  // ValidateUpdate is the default update validation for an end user.
   142  func (podStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   143  	// Allow downward api usage of hugepages on pod update if feature is enabled or if the old pod already had used them.
   144  	pod := obj.(*api.Pod)
   145  	oldPod := old.(*api.Pod)
   146  	opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta)
   147  	opts.ResourceIsPod = true
   148  	return corevalidation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod), opts)
   149  }
   150  
   151  // WarningsOnUpdate returns warnings for the given update.
   152  func (podStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   153  	// skip warnings on pod update, since humans don't typically interact directly with pods,
   154  	// and we don't want to pay the evaluation cost on what might be a high-frequency update path
   155  	return nil
   156  }
   157  
   158  // AllowUnconditionalUpdate allows pods to be overwritten
   159  func (podStrategy) AllowUnconditionalUpdate() bool {
   160  	return true
   161  }
   162  
   163  // CheckGracefulDelete allows a pod to be gracefully deleted. It updates the DeleteOptions to
   164  // reflect the desired grace value.
   165  func (podStrategy) CheckGracefulDelete(ctx context.Context, obj runtime.Object, options *metav1.DeleteOptions) bool {
   166  	if options == nil {
   167  		return false
   168  	}
   169  	pod := obj.(*api.Pod)
   170  	period := int64(0)
   171  	// user has specified a value
   172  	if options.GracePeriodSeconds != nil {
   173  		period = *options.GracePeriodSeconds
   174  	} else {
   175  		// use the default value if set, or deletes the pod immediately (0)
   176  		if pod.Spec.TerminationGracePeriodSeconds != nil {
   177  			period = *pod.Spec.TerminationGracePeriodSeconds
   178  		}
   179  	}
   180  	// if the pod is not scheduled, delete immediately
   181  	if len(pod.Spec.NodeName) == 0 {
   182  		period = 0
   183  	}
   184  	// if the pod is already terminated, delete immediately
   185  	if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded {
   186  		period = 0
   187  	}
   188  
   189  	if period < 0 {
   190  		period = 1
   191  	}
   192  
   193  	// ensure the options and the pod are in sync
   194  	options.GracePeriodSeconds = &period
   195  	return true
   196  }
   197  
   198  type podStatusStrategy struct {
   199  	podStrategy
   200  }
   201  
   202  // StatusStrategy wraps and exports the used podStrategy for the storage package.
   203  var StatusStrategy = podStatusStrategy{Strategy}
   204  
   205  // GetResetFields returns the set of fields that get reset by the strategy
   206  // and should not be modified by the user.
   207  func (podStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
   208  	return map[fieldpath.APIVersion]*fieldpath.Set{
   209  		"v1": fieldpath.NewSet(
   210  			fieldpath.MakePathOrDie("spec"),
   211  			fieldpath.MakePathOrDie("metadata", "deletionTimestamp"),
   212  			fieldpath.MakePathOrDie("metadata", "ownerReferences"),
   213  		),
   214  	}
   215  }
   216  
   217  func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
   218  	newPod := obj.(*api.Pod)
   219  	oldPod := old.(*api.Pod)
   220  	newPod.Spec = oldPod.Spec
   221  	newPod.DeletionTimestamp = nil
   222  
   223  	// don't allow the pods/status endpoint to touch owner references since old kubelets corrupt them in a way
   224  	// that breaks garbage collection
   225  	newPod.OwnerReferences = oldPod.OwnerReferences
   226  }
   227  
   228  func (podStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   229  	pod := obj.(*api.Pod)
   230  	oldPod := old.(*api.Pod)
   231  	opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&pod.Spec, &oldPod.Spec, &pod.ObjectMeta, &oldPod.ObjectMeta)
   232  	opts.ResourceIsPod = true
   233  
   234  	return corevalidation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod), opts)
   235  }
   236  
   237  // WarningsOnUpdate returns warnings for the given update.
   238  func (podStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   239  	return nil
   240  }
   241  
   242  type podEphemeralContainersStrategy struct {
   243  	podStrategy
   244  }
   245  
   246  // EphemeralContainersStrategy wraps and exports the used podStrategy for the storage package.
   247  var EphemeralContainersStrategy = podEphemeralContainersStrategy{Strategy}
   248  
   249  // dropNonEphemeralContainerUpdates discards all changes except for pod.Spec.EphemeralContainers and certain metadata
   250  func dropNonEphemeralContainerUpdates(newPod, oldPod *api.Pod) *api.Pod {
   251  	pod := oldPod.DeepCopy()
   252  	pod.Name = newPod.Name
   253  	pod.Namespace = newPod.Namespace
   254  	pod.ResourceVersion = newPod.ResourceVersion
   255  	pod.UID = newPod.UID
   256  	pod.Spec.EphemeralContainers = newPod.Spec.EphemeralContainers
   257  	return pod
   258  }
   259  
   260  func (podEphemeralContainersStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
   261  	newPod := obj.(*api.Pod)
   262  	oldPod := old.(*api.Pod)
   263  
   264  	*newPod = *dropNonEphemeralContainerUpdates(newPod, oldPod)
   265  	podutil.DropDisabledPodFields(newPod, oldPod)
   266  }
   267  
   268  func (podEphemeralContainersStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   269  	newPod := obj.(*api.Pod)
   270  	oldPod := old.(*api.Pod)
   271  	opts := podutil.GetValidationOptionsFromPodSpecAndMeta(&newPod.Spec, &oldPod.Spec, &newPod.ObjectMeta, &oldPod.ObjectMeta)
   272  	opts.ResourceIsPod = true
   273  	return corevalidation.ValidatePodEphemeralContainersUpdate(newPod, oldPod, opts)
   274  }
   275  
   276  // WarningsOnUpdate returns warnings for the given update.
   277  func (podEphemeralContainersStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   278  	return nil
   279  }
   280  
   281  // GetAttrs returns labels and fields of a given object for filtering purposes.
   282  func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
   283  	pod, ok := obj.(*api.Pod)
   284  	if !ok {
   285  		return nil, nil, fmt.Errorf("not a pod")
   286  	}
   287  	return labels.Set(pod.ObjectMeta.Labels), ToSelectableFields(pod), nil
   288  }
   289  
   290  // MatchPod returns a generic matcher for a given label and field selector.
   291  func MatchPod(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
   292  	return storage.SelectionPredicate{
   293  		Label:       label,
   294  		Field:       field,
   295  		GetAttrs:    GetAttrs,
   296  		IndexFields: []string{"spec.nodeName"},
   297  	}
   298  }
   299  
   300  // NodeNameTriggerFunc returns value spec.nodename of given object.
   301  func NodeNameTriggerFunc(obj runtime.Object) string {
   302  	return obj.(*api.Pod).Spec.NodeName
   303  }
   304  
   305  // NodeNameIndexFunc return value spec.nodename of given object.
   306  func NodeNameIndexFunc(obj interface{}) ([]string, error) {
   307  	pod, ok := obj.(*api.Pod)
   308  	if !ok {
   309  		return nil, fmt.Errorf("not a pod")
   310  	}
   311  	return []string{pod.Spec.NodeName}, nil
   312  }
   313  
   314  // Indexers returns the indexers for pod storage.
   315  func Indexers() *cache.Indexers {
   316  	return &cache.Indexers{
   317  		storage.FieldIndex("spec.nodeName"): NodeNameIndexFunc,
   318  	}
   319  }
   320  
   321  // ToSelectableFields returns a field set that represents the object
   322  // TODO: fields are not labels, and the validation rules for them do not apply.
   323  func ToSelectableFields(pod *api.Pod) fields.Set {
   324  	// The purpose of allocation with a given number of elements is to reduce
   325  	// amount of allocations needed to create the fields.Set. If you add any
   326  	// field here or the number of object-meta related fields changes, this should
   327  	// be adjusted.
   328  	podSpecificFieldsSet := make(fields.Set, 10)
   329  	podSpecificFieldsSet["spec.nodeName"] = pod.Spec.NodeName
   330  	podSpecificFieldsSet["spec.restartPolicy"] = string(pod.Spec.RestartPolicy)
   331  	podSpecificFieldsSet["spec.schedulerName"] = string(pod.Spec.SchedulerName)
   332  	podSpecificFieldsSet["spec.serviceAccountName"] = string(pod.Spec.ServiceAccountName)
   333  	if pod.Spec.SecurityContext != nil {
   334  		podSpecificFieldsSet["spec.hostNetwork"] = strconv.FormatBool(pod.Spec.SecurityContext.HostNetwork)
   335  	} else {
   336  		// default to false
   337  		podSpecificFieldsSet["spec.hostNetwork"] = strconv.FormatBool(false)
   338  	}
   339  	podSpecificFieldsSet["status.phase"] = string(pod.Status.Phase)
   340  	// TODO: add podIPs as a downward API value(s) with proper format
   341  	podIP := ""
   342  	if len(pod.Status.PodIPs) > 0 {
   343  		podIP = string(pod.Status.PodIPs[0].IP)
   344  	}
   345  	podSpecificFieldsSet["status.podIP"] = podIP
   346  	podSpecificFieldsSet["status.nominatedNodeName"] = string(pod.Status.NominatedNodeName)
   347  	return generic.AddObjectMetaFieldsSet(podSpecificFieldsSet, &pod.ObjectMeta, true)
   348  }
   349  
   350  // ResourceGetter is an interface for retrieving resources by ResourceLocation.
   351  type ResourceGetter interface {
   352  	Get(context.Context, string, *metav1.GetOptions) (runtime.Object, error)
   353  }
   354  
   355  func getPod(ctx context.Context, getter ResourceGetter, name string) (*api.Pod, error) {
   356  	obj, err := getter.Get(ctx, name, &metav1.GetOptions{})
   357  	if err != nil {
   358  		return nil, err
   359  	}
   360  	pod := obj.(*api.Pod)
   361  	if pod == nil {
   362  		return nil, fmt.Errorf("Unexpected object type: %#v", pod)
   363  	}
   364  	return pod, nil
   365  }
   366  
   367  // getPodIP returns primary IP for a Pod
   368  func getPodIP(pod *api.Pod) string {
   369  	if pod == nil {
   370  		return ""
   371  	}
   372  	if len(pod.Status.PodIPs) > 0 {
   373  		return pod.Status.PodIPs[0].IP
   374  	}
   375  
   376  	return ""
   377  }
   378  
   379  // ResourceLocation returns a URL to which one can send traffic for the specified pod.
   380  func ResourceLocation(ctx context.Context, getter ResourceGetter, rt http.RoundTripper, id string) (*url.URL, http.RoundTripper, error) {
   381  	// Allow ID as "podname" or "podname:port" or "scheme:podname:port".
   382  	// If port is not specified, try to use the first defined port on the pod.
   383  	scheme, name, port, valid := utilnet.SplitSchemeNamePort(id)
   384  	if !valid {
   385  		return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id))
   386  	}
   387  
   388  	pod, err := getPod(ctx, getter, name)
   389  	if err != nil {
   390  		return nil, nil, err
   391  	}
   392  
   393  	// Try to figure out a port.
   394  	if port == "" {
   395  		for i := range pod.Spec.Containers {
   396  			if len(pod.Spec.Containers[i].Ports) > 0 {
   397  				port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort)
   398  				break
   399  			}
   400  		}
   401  	}
   402  	podIP := getPodIP(pod)
   403  	if ip := netutils.ParseIPSloppy(podIP); ip == nil || !ip.IsGlobalUnicast() {
   404  		return nil, nil, errors.NewBadRequest("address not allowed")
   405  	}
   406  
   407  	loc := &url.URL{
   408  		Scheme: scheme,
   409  	}
   410  	if port == "" {
   411  		// when using an ipv6 IP as a hostname in a URL, it must be wrapped in [...]
   412  		// net.JoinHostPort does this for you.
   413  		if strings.Contains(podIP, ":") {
   414  			loc.Host = "[" + podIP + "]"
   415  		} else {
   416  			loc.Host = podIP
   417  		}
   418  	} else {
   419  		loc.Host = net.JoinHostPort(podIP, port)
   420  	}
   421  	return loc, rt, nil
   422  }
   423  
   424  // LogLocation returns the log URL for a pod container. If opts.Container is blank
   425  // and only one container is present in the pod, that container is used.
   426  func LogLocation(
   427  	ctx context.Context, getter ResourceGetter,
   428  	connInfo client.ConnectionInfoGetter,
   429  	name string,
   430  	opts *api.PodLogOptions,
   431  ) (*url.URL, http.RoundTripper, error) {
   432  	pod, err := getPod(ctx, getter, name)
   433  	if err != nil {
   434  		return nil, nil, err
   435  	}
   436  
   437  	// Try to figure out a container
   438  	// If a container was provided, it must be valid
   439  	container := opts.Container
   440  	container, err = validateContainer(container, pod)
   441  	if err != nil {
   442  		return nil, nil, err
   443  	}
   444  	nodeName := types.NodeName(pod.Spec.NodeName)
   445  	if len(nodeName) == 0 {
   446  		// If pod has not been assigned a host, return an empty location
   447  		return nil, nil, nil
   448  	}
   449  	nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)
   450  	if err != nil {
   451  		return nil, nil, err
   452  	}
   453  	params := url.Values{}
   454  	if opts.Follow {
   455  		params.Add("follow", "true")
   456  	}
   457  	if opts.Previous {
   458  		params.Add("previous", "true")
   459  	}
   460  	if opts.Timestamps {
   461  		params.Add("timestamps", "true")
   462  	}
   463  	if opts.SinceSeconds != nil {
   464  		params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10))
   465  	}
   466  	if opts.SinceTime != nil {
   467  		params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339))
   468  	}
   469  	if opts.TailLines != nil {
   470  		params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10))
   471  	}
   472  	if opts.LimitBytes != nil {
   473  		params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10))
   474  	}
   475  	loc := &url.URL{
   476  		Scheme:   nodeInfo.Scheme,
   477  		Host:     net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port),
   478  		Path:     fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container),
   479  		RawQuery: params.Encode(),
   480  	}
   481  
   482  	if opts.InsecureSkipTLSVerifyBackend {
   483  		return loc, nodeInfo.InsecureSkipTLSVerifyTransport, nil
   484  	}
   485  	return loc, nodeInfo.Transport, nil
   486  }
   487  
   488  func podHasContainerWithName(pod *api.Pod, containerName string) bool {
   489  	var hasContainer bool
   490  	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *api.Container, containerType podutil.ContainerType) bool {
   491  		if c.Name == containerName {
   492  			hasContainer = true
   493  			return false
   494  		}
   495  		return true
   496  	})
   497  	return hasContainer
   498  }
   499  
   500  func streamParams(params url.Values, opts runtime.Object) error {
   501  	switch opts := opts.(type) {
   502  	case *api.PodExecOptions:
   503  		if opts.Stdin {
   504  			params.Add(api.ExecStdinParam, "1")
   505  		}
   506  		if opts.Stdout {
   507  			params.Add(api.ExecStdoutParam, "1")
   508  		}
   509  		if opts.Stderr {
   510  			params.Add(api.ExecStderrParam, "1")
   511  		}
   512  		if opts.TTY {
   513  			params.Add(api.ExecTTYParam, "1")
   514  		}
   515  		for _, c := range opts.Command {
   516  			params.Add("command", c)
   517  		}
   518  	case *api.PodAttachOptions:
   519  		if opts.Stdin {
   520  			params.Add(api.ExecStdinParam, "1")
   521  		}
   522  		if opts.Stdout {
   523  			params.Add(api.ExecStdoutParam, "1")
   524  		}
   525  		if opts.Stderr {
   526  			params.Add(api.ExecStderrParam, "1")
   527  		}
   528  		if opts.TTY {
   529  			params.Add(api.ExecTTYParam, "1")
   530  		}
   531  	case *api.PodPortForwardOptions:
   532  		if len(opts.Ports) > 0 {
   533  			ports := make([]string, len(opts.Ports))
   534  			for i, p := range opts.Ports {
   535  				ports[i] = strconv.FormatInt(int64(p), 10)
   536  			}
   537  			params.Add(api.PortHeader, strings.Join(ports, ","))
   538  		}
   539  	default:
   540  		return fmt.Errorf("Unknown object for streaming: %v", opts)
   541  	}
   542  	return nil
   543  }
   544  
   545  // AttachLocation returns the attach URL for a pod container. If opts.Container is blank
   546  // and only one container is present in the pod, that container is used.
   547  func AttachLocation(
   548  	ctx context.Context,
   549  	getter ResourceGetter,
   550  	connInfo client.ConnectionInfoGetter,
   551  	name string,
   552  	opts *api.PodAttachOptions,
   553  ) (*url.URL, http.RoundTripper, error) {
   554  	return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "attach")
   555  }
   556  
   557  // ExecLocation returns the exec URL for a pod container. If opts.Container is blank
   558  // and only one container is present in the pod, that container is used.
   559  func ExecLocation(
   560  	ctx context.Context,
   561  	getter ResourceGetter,
   562  	connInfo client.ConnectionInfoGetter,
   563  	name string,
   564  	opts *api.PodExecOptions,
   565  ) (*url.URL, http.RoundTripper, error) {
   566  	return streamLocation(ctx, getter, connInfo, name, opts, opts.Container, "exec")
   567  }
   568  
   569  func streamLocation(
   570  	ctx context.Context,
   571  	getter ResourceGetter,
   572  	connInfo client.ConnectionInfoGetter,
   573  	name string,
   574  	opts runtime.Object,
   575  	container,
   576  	path string,
   577  ) (*url.URL, http.RoundTripper, error) {
   578  	pod, err := getPod(ctx, getter, name)
   579  	if err != nil {
   580  		return nil, nil, err
   581  	}
   582  
   583  	// Try to figure out a container
   584  	// If a container was provided, it must be valid
   585  	container, err = validateContainer(container, pod)
   586  	if err != nil {
   587  		return nil, nil, err
   588  	}
   589  
   590  	nodeName := types.NodeName(pod.Spec.NodeName)
   591  	if len(nodeName) == 0 {
   592  		// If pod has not been assigned a host, return an empty location
   593  		return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
   594  	}
   595  	nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)
   596  	if err != nil {
   597  		return nil, nil, err
   598  	}
   599  	params := url.Values{}
   600  	if err := streamParams(params, opts); err != nil {
   601  		return nil, nil, err
   602  	}
   603  	loc := &url.URL{
   604  		Scheme:   nodeInfo.Scheme,
   605  		Host:     net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port),
   606  		Path:     fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),
   607  		RawQuery: params.Encode(),
   608  	}
   609  	return loc, nodeInfo.Transport, nil
   610  }
   611  
   612  // PortForwardLocation returns the port-forward URL for a pod.
   613  func PortForwardLocation(
   614  	ctx context.Context,
   615  	getter ResourceGetter,
   616  	connInfo client.ConnectionInfoGetter,
   617  	name string,
   618  	opts *api.PodPortForwardOptions,
   619  ) (*url.URL, http.RoundTripper, error) {
   620  	pod, err := getPod(ctx, getter, name)
   621  	if err != nil {
   622  		return nil, nil, err
   623  	}
   624  
   625  	nodeName := types.NodeName(pod.Spec.NodeName)
   626  	if len(nodeName) == 0 {
   627  		// If pod has not been assigned a host, return an empty location
   628  		return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
   629  	}
   630  	nodeInfo, err := connInfo.GetConnectionInfo(ctx, nodeName)
   631  	if err != nil {
   632  		return nil, nil, err
   633  	}
   634  	params := url.Values{}
   635  	if err := streamParams(params, opts); err != nil {
   636  		return nil, nil, err
   637  	}
   638  	loc := &url.URL{
   639  		Scheme:   nodeInfo.Scheme,
   640  		Host:     net.JoinHostPort(nodeInfo.Hostname, nodeInfo.Port),
   641  		Path:     fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name),
   642  		RawQuery: params.Encode(),
   643  	}
   644  	return loc, nodeInfo.Transport, nil
   645  }
   646  
   647  // validateContainer validate container is valid for pod, return valid container
   648  func validateContainer(container string, pod *api.Pod) (string, error) {
   649  	if len(container) == 0 {
   650  		switch len(pod.Spec.Containers) {
   651  		case 1:
   652  			container = pod.Spec.Containers[0].Name
   653  		case 0:
   654  			return "", errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", pod.Name))
   655  		default:
   656  			var containerNames []string
   657  			podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(c *api.Container, containerType podutil.ContainerType) bool {
   658  				containerNames = append(containerNames, c.Name)
   659  				return true
   660  			})
   661  			errStr := fmt.Sprintf("a container name must be specified for pod %s, choose one of: %s", pod.Name, containerNames)
   662  			return "", errors.NewBadRequest(errStr)
   663  		}
   664  	} else {
   665  		if !podHasContainerWithName(pod, container) {
   666  			return "", errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, pod.Name))
   667  		}
   668  	}
   669  
   670  	return container, nil
   671  }
   672  
   673  // applyLabelKeysToLabelSelector obtains the label value from the given label set by the key in labelKeys,
   674  // and merge to LabelSelector with the given operator:
   675  func applyLabelKeysToLabelSelector(labelSelector *metav1.LabelSelector, labelKeys []string, operator metav1.LabelSelectorOperator, podLabels map[string]string) {
   676  	for _, key := range labelKeys {
   677  		if value, ok := podLabels[key]; ok {
   678  			labelSelector.MatchExpressions = append(labelSelector.MatchExpressions, metav1.LabelSelectorRequirement{
   679  				Key:      key,
   680  				Operator: operator,
   681  				Values:   []string{value},
   682  			})
   683  		}
   684  	}
   685  }
   686  
   687  // applyMatchLabelKeysAndMismatchLabelKeys obtains the labels from the pod labels by the key in matchLabelKeys or mismatchLabelKeys,
   688  // and merge to LabelSelector of PodAffinityTerm depending on field:
   689  // - If matchLabelKeys, key in (value) is merged with LabelSelector.
   690  // - If mismatchLabelKeys, key notin (value) is merged with LabelSelector.
   691  func applyMatchLabelKeysAndMismatchLabelKeys(term *api.PodAffinityTerm, label map[string]string) {
   692  	if (len(term.MatchLabelKeys) == 0 && len(term.MismatchLabelKeys) == 0) || term.LabelSelector == nil {
   693  		// If LabelSelector is nil, we don't need to apply label keys to it because nil-LabelSelector is match none.
   694  		return
   695  	}
   696  
   697  	applyLabelKeysToLabelSelector(term.LabelSelector, term.MatchLabelKeys, metav1.LabelSelectorOpIn, label)
   698  	applyLabelKeysToLabelSelector(term.LabelSelector, term.MismatchLabelKeys, metav1.LabelSelectorOpNotIn, label)
   699  }
   700  
   701  func mutatePodAffinity(pod *api.Pod) {
   702  	if !utilfeature.DefaultFeatureGate.Enabled(features.MatchLabelKeysInPodAffinity) || pod.Spec.Affinity == nil {
   703  		return
   704  	}
   705  	if affinity := pod.Spec.Affinity.PodAffinity; affinity != nil {
   706  		for i := range affinity.PreferredDuringSchedulingIgnoredDuringExecution {
   707  			applyMatchLabelKeysAndMismatchLabelKeys(&affinity.PreferredDuringSchedulingIgnoredDuringExecution[i].PodAffinityTerm, pod.Labels)
   708  		}
   709  		for i := range affinity.RequiredDuringSchedulingIgnoredDuringExecution {
   710  			applyMatchLabelKeysAndMismatchLabelKeys(&affinity.RequiredDuringSchedulingIgnoredDuringExecution[i], pod.Labels)
   711  		}
   712  	}
   713  	if affinity := pod.Spec.Affinity.PodAntiAffinity; affinity != nil {
   714  		for i := range affinity.PreferredDuringSchedulingIgnoredDuringExecution {
   715  			applyMatchLabelKeysAndMismatchLabelKeys(&affinity.PreferredDuringSchedulingIgnoredDuringExecution[i].PodAffinityTerm, pod.Labels)
   716  		}
   717  		for i := range affinity.RequiredDuringSchedulingIgnoredDuringExecution {
   718  			applyMatchLabelKeysAndMismatchLabelKeys(&affinity.RequiredDuringSchedulingIgnoredDuringExecution[i], pod.Labels)
   719  		}
   720  	}
   721  }
   722  
   723  // applyWaitingForSchedulingGatesCondition adds a {type:PodScheduled, reason:WaitingForGates} condition
   724  // to a new-created Pod if necessary.
   725  func applyWaitingForSchedulingGatesCondition(pod *api.Pod) {
   726  	if !utilfeature.DefaultFeatureGate.Enabled(features.PodSchedulingReadiness) ||
   727  		len(pod.Spec.SchedulingGates) == 0 {
   728  		return
   729  	}
   730  
   731  	// If found a condition with type PodScheduled, return.
   732  	for _, condition := range pod.Status.Conditions {
   733  		if condition.Type == api.PodScheduled {
   734  			return
   735  		}
   736  	}
   737  
   738  	pod.Status.Conditions = append(pod.Status.Conditions, api.PodCondition{
   739  		Type:    api.PodScheduled,
   740  		Status:  api.ConditionFalse,
   741  		Reason:  apiv1.PodReasonSchedulingGated,
   742  		Message: "Scheduling is blocked due to non-empty scheduling gates",
   743  	})
   744  }