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 }