123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502 |
- /*
- Copyright 2014 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package pod
- import (
- "fmt"
- "net"
- "net/http"
- "net/url"
- "strconv"
- "strings"
- "time"
- "k8s.io/kubernetes/pkg/api"
- "k8s.io/kubernetes/pkg/api/errors"
- "k8s.io/kubernetes/pkg/api/validation"
- "k8s.io/kubernetes/pkg/fields"
- "k8s.io/kubernetes/pkg/kubelet/client"
- "k8s.io/kubernetes/pkg/labels"
- "k8s.io/kubernetes/pkg/registry/generic"
- "k8s.io/kubernetes/pkg/runtime"
- "k8s.io/kubernetes/pkg/storage"
- utilnet "k8s.io/kubernetes/pkg/util/net"
- "k8s.io/kubernetes/pkg/util/validation/field"
- )
- // podStrategy implements behavior for Pods
- type podStrategy struct {
- runtime.ObjectTyper
- api.NameGenerator
- }
- // Strategy is the default logic that applies when creating and updating Pod
- // objects via the REST API.
- var Strategy = podStrategy{api.Scheme, api.SimpleNameGenerator}
- // NamespaceScoped is true for pods.
- func (podStrategy) NamespaceScoped() bool {
- return true
- }
- // PrepareForCreate clears fields that are not allowed to be set by end users on creation.
- func (podStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) {
- pod := obj.(*api.Pod)
- pod.Status = api.PodStatus{
- Phase: api.PodPending,
- }
- }
- // PrepareForUpdate clears fields that are not allowed to be set by end users on update.
- func (podStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
- newPod := obj.(*api.Pod)
- oldPod := old.(*api.Pod)
- newPod.Status = oldPod.Status
- }
- // Validate validates a new pod.
- func (podStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
- pod := obj.(*api.Pod)
- return validation.ValidatePod(pod)
- }
- // Canonicalize normalizes the object after validation.
- func (podStrategy) Canonicalize(obj runtime.Object) {
- }
- // AllowCreateOnUpdate is false for pods.
- func (podStrategy) AllowCreateOnUpdate() bool {
- return false
- }
- // ValidateUpdate is the default update validation for an end user.
- func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
- errorList := validation.ValidatePod(obj.(*api.Pod))
- return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
- }
- // AllowUnconditionalUpdate allows pods to be overwritten
- func (podStrategy) AllowUnconditionalUpdate() bool {
- return true
- }
- // CheckGracefulDelete allows a pod to be gracefully deleted. It updates the DeleteOptions to
- // reflect the desired grace value.
- func (podStrategy) CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool {
- if options == nil {
- return false
- }
- pod := obj.(*api.Pod)
- period := int64(0)
- // user has specified a value
- if options.GracePeriodSeconds != nil {
- period = *options.GracePeriodSeconds
- } else {
- // use the default value if set, or deletes the pod immediately (0)
- if pod.Spec.TerminationGracePeriodSeconds != nil {
- period = *pod.Spec.TerminationGracePeriodSeconds
- }
- }
- // if the pod is not scheduled, delete immediately
- if len(pod.Spec.NodeName) == 0 {
- period = 0
- }
- // if the pod is already terminated, delete immediately
- if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded {
- period = 0
- }
- // ensure the options and the pod are in sync
- options.GracePeriodSeconds = &period
- return true
- }
- type podStrategyWithoutGraceful struct {
- podStrategy
- }
- // CheckGracefulDelete prohibits graceful deletion.
- func (podStrategyWithoutGraceful) CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool {
- return false
- }
- // StrategyWithoutGraceful implements the legacy instant delele behavior.
- var StrategyWithoutGraceful = podStrategyWithoutGraceful{Strategy}
- type podStatusStrategy struct {
- podStrategy
- }
- var StatusStrategy = podStatusStrategy{Strategy}
- func (podStatusStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
- newPod := obj.(*api.Pod)
- oldPod := old.(*api.Pod)
- newPod.Spec = oldPod.Spec
- newPod.DeletionTimestamp = nil
- }
- func (podStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
- // TODO: merge valid fields after update
- return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
- }
- // MatchPod returns a generic matcher for a given label and field selector.
- func MatchPod(label labels.Selector, field fields.Selector) *generic.SelectionPredicate {
- return &generic.SelectionPredicate{
- Label: label,
- Field: field,
- GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
- pod, ok := obj.(*api.Pod)
- if !ok {
- return nil, nil, fmt.Errorf("not a pod")
- }
- // podLabels is already sitting there ready to be used.
- // podFields is not available directly and requires allocation of a map.
- // Only bother if the fields might be useful to determining the match.
- // One common case is for a replication controller to set up a watch
- // based on labels alone; in that case we can avoid allocating the field map.
- // This is especially important in the apiserver.
- podLabels := labels.Set(pod.ObjectMeta.Labels)
- var podFields fields.Set
- if !field.Empty() && label.Matches(podLabels) {
- podFields = PodToSelectableFields(pod)
- }
- return podLabels, podFields, nil
- },
- IndexFields: []string{"spec.nodeName"},
- }
- }
- func NodeNameTriggerFunc(obj runtime.Object) []storage.MatchValue {
- pod := obj.(*api.Pod)
- result := storage.MatchValue{IndexName: "spec.nodeName", Value: pod.Spec.NodeName}
- return []storage.MatchValue{result}
- }
- // PodToSelectableFields returns a field set that represents the object
- // TODO: fields are not labels, and the validation rules for them do not apply.
- func PodToSelectableFields(pod *api.Pod) fields.Set {
- objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true)
- podSpecificFieldsSet := fields.Set{
- "spec.nodeName": pod.Spec.NodeName,
- "spec.restartPolicy": string(pod.Spec.RestartPolicy),
- "status.phase": string(pod.Status.Phase),
- }
- return generic.MergeFieldsSets(objectMetaFieldsSet, podSpecificFieldsSet)
- }
- // ResourceGetter is an interface for retrieving resources by ResourceLocation.
- type ResourceGetter interface {
- Get(api.Context, string) (runtime.Object, error)
- }
- func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) {
- obj, err := getter.Get(ctx, name)
- if err != nil {
- return nil, err
- }
- pod := obj.(*api.Pod)
- if pod == nil {
- return nil, fmt.Errorf("Unexpected object type: %#v", pod)
- }
- return pod, nil
- }
- // ResourceLocation returns a URL to which one can send traffic for the specified pod.
- func ResourceLocation(getter ResourceGetter, rt http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
- // Allow ID as "podname" or "podname:port" or "scheme:podname:port".
- // If port is not specified, try to use the first defined port on the pod.
- scheme, name, port, valid := utilnet.SplitSchemeNamePort(id)
- if !valid {
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id))
- }
- // TODO: if port is not a number but a "(container)/(portname)", do a name lookup.
- pod, err := getPod(getter, ctx, name)
- if err != nil {
- return nil, nil, err
- }
- // Try to figure out a port.
- if port == "" {
- for i := range pod.Spec.Containers {
- if len(pod.Spec.Containers[i].Ports) > 0 {
- port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort)
- break
- }
- }
- }
- loc := &url.URL{
- Scheme: scheme,
- }
- if port == "" {
- loc.Host = pod.Status.PodIP
- } else {
- loc.Host = net.JoinHostPort(pod.Status.PodIP, port)
- }
- return loc, rt, nil
- }
- // getContainerNames returns a formatted string containing the container names
- func getContainerNames(containers []api.Container) string {
- names := []string{}
- for _, c := range containers {
- names = append(names, c.Name)
- }
- return strings.Join(names, " ")
- }
- // LogLocation returns the log URL for a pod container. If opts.Container is blank
- // and only one container is present in the pod, that container is used.
- func LogLocation(
- getter ResourceGetter,
- connInfo client.ConnectionInfoGetter,
- ctx api.Context,
- name string,
- opts *api.PodLogOptions,
- ) (*url.URL, http.RoundTripper, error) {
- pod, err := getPod(getter, ctx, name)
- if err != nil {
- return nil, nil, err
- }
- // Try to figure out a container
- // If a container was provided, it must be valid
- container := opts.Container
- if len(container) == 0 {
- switch len(pod.Spec.Containers) {
- case 1:
- container = pod.Spec.Containers[0].Name
- case 0:
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
- default:
- containerNames := getContainerNames(pod.Spec.Containers)
- initContainerNames := getContainerNames(pod.Spec.InitContainers)
- err := fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)
- if len(initContainerNames) > 0 {
- err += fmt.Sprintf(" or one of the init containers: [%s]", initContainerNames)
- }
- return nil, nil, errors.NewBadRequest(err)
- }
- } else {
- if !podHasContainerWithName(pod, container) {
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name))
- }
- }
- nodeHost := pod.Spec.NodeName
- if len(nodeHost) == 0 {
- // If pod has not been assigned a host, return an empty location
- return nil, nil, nil
- }
- nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
- if err != nil {
- return nil, nil, err
- }
- params := url.Values{}
- if opts.Follow {
- params.Add("follow", "true")
- }
- if opts.Previous {
- params.Add("previous", "true")
- }
- if opts.Timestamps {
- params.Add("timestamps", "true")
- }
- if opts.SinceSeconds != nil {
- params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10))
- }
- if opts.SinceTime != nil {
- params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339))
- }
- if opts.TailLines != nil {
- params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10))
- }
- if opts.LimitBytes != nil {
- params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10))
- }
- loc := &url.URL{
- Scheme: nodeScheme,
- Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
- Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container),
- RawQuery: params.Encode(),
- }
- return loc, nodeTransport, nil
- }
- func podHasContainerWithName(pod *api.Pod, containerName string) bool {
- for _, c := range pod.Spec.Containers {
- if c.Name == containerName {
- return true
- }
- }
- for _, c := range pod.Spec.InitContainers {
- if c.Name == containerName {
- return true
- }
- }
- return false
- }
- func streamParams(params url.Values, opts runtime.Object) error {
- switch opts := opts.(type) {
- case *api.PodExecOptions:
- if opts.Stdin {
- params.Add(api.ExecStdinParam, "1")
- }
- if opts.Stdout {
- params.Add(api.ExecStdoutParam, "1")
- }
- if opts.Stderr {
- params.Add(api.ExecStderrParam, "1")
- }
- if opts.TTY {
- params.Add(api.ExecTTYParam, "1")
- }
- for _, c := range opts.Command {
- params.Add("command", c)
- }
- case *api.PodAttachOptions:
- if opts.Stdin {
- params.Add(api.ExecStdinParam, "1")
- }
- if opts.Stdout {
- params.Add(api.ExecStdoutParam, "1")
- }
- if opts.Stderr {
- params.Add(api.ExecStderrParam, "1")
- }
- if opts.TTY {
- params.Add(api.ExecTTYParam, "1")
- }
- default:
- return fmt.Errorf("Unknown object for streaming: %v", opts)
- }
- return nil
- }
- // AttachLocation returns the attach URL for a pod container. If opts.Container is blank
- // and only one container is present in the pod, that container is used.
- func AttachLocation(
- getter ResourceGetter,
- connInfo client.ConnectionInfoGetter,
- ctx api.Context,
- name string,
- opts *api.PodAttachOptions,
- ) (*url.URL, http.RoundTripper, error) {
- return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "attach")
- }
- // ExecLocation returns the exec URL for a pod container. If opts.Container is blank
- // and only one container is present in the pod, that container is used.
- func ExecLocation(
- getter ResourceGetter,
- connInfo client.ConnectionInfoGetter,
- ctx api.Context,
- name string,
- opts *api.PodExecOptions,
- ) (*url.URL, http.RoundTripper, error) {
- return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "exec")
- }
- func streamLocation(
- getter ResourceGetter,
- connInfo client.ConnectionInfoGetter,
- ctx api.Context,
- name string,
- opts runtime.Object,
- container,
- path string,
- ) (*url.URL, http.RoundTripper, error) {
- pod, err := getPod(getter, ctx, name)
- if err != nil {
- return nil, nil, err
- }
- // Try to figure out a container
- // If a container was provided, it must be valid
- if container == "" {
- switch len(pod.Spec.Containers) {
- case 1:
- container = pod.Spec.Containers[0].Name
- case 0:
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
- default:
- containerNames := getContainerNames(pod.Spec.Containers)
- initContainerNames := getContainerNames(pod.Spec.InitContainers)
- err := fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)
- if len(initContainerNames) > 0 {
- err += fmt.Sprintf(" or one of the init containers: [%s]", initContainerNames)
- }
- return nil, nil, errors.NewBadRequest(err)
- }
- } else {
- if !podHasContainerWithName(pod, container) {
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name))
- }
- }
- nodeHost := pod.Spec.NodeName
- if len(nodeHost) == 0 {
- // If pod has not been assigned a host, return an empty location
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
- }
- nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
- if err != nil {
- return nil, nil, err
- }
- params := url.Values{}
- if err := streamParams(params, opts); err != nil {
- return nil, nil, err
- }
- loc := &url.URL{
- Scheme: nodeScheme,
- Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
- Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),
- RawQuery: params.Encode(),
- }
- return loc, nodeTransport, nil
- }
- // PortForwardLocation returns the port-forward URL for a pod.
- func PortForwardLocation(
- getter ResourceGetter,
- connInfo client.ConnectionInfoGetter,
- ctx api.Context,
- name string,
- ) (*url.URL, http.RoundTripper, error) {
- pod, err := getPod(getter, ctx, name)
- if err != nil {
- return nil, nil, err
- }
- nodeHost := pod.Spec.NodeName
- if len(nodeHost) == 0 {
- // If pod has not been assigned a host, return an empty location
- return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
- }
- nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
- if err != nil {
- return nil, nil, err
- }
- loc := &url.URL{
- Scheme: nodeScheme,
- Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
- Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name),
- }
- return loc, nodeTransport, nil
- }
|