strategy.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package pod
  14. import (
  15. "fmt"
  16. "net"
  17. "net/http"
  18. "net/url"
  19. "strconv"
  20. "strings"
  21. "time"
  22. "k8s.io/kubernetes/pkg/api"
  23. "k8s.io/kubernetes/pkg/api/errors"
  24. "k8s.io/kubernetes/pkg/api/validation"
  25. "k8s.io/kubernetes/pkg/fields"
  26. "k8s.io/kubernetes/pkg/kubelet/client"
  27. "k8s.io/kubernetes/pkg/labels"
  28. "k8s.io/kubernetes/pkg/registry/generic"
  29. "k8s.io/kubernetes/pkg/runtime"
  30. "k8s.io/kubernetes/pkg/storage"
  31. utilnet "k8s.io/kubernetes/pkg/util/net"
  32. "k8s.io/kubernetes/pkg/util/validation/field"
  33. )
  34. // podStrategy implements behavior for Pods
  35. type podStrategy struct {
  36. runtime.ObjectTyper
  37. api.NameGenerator
  38. }
  39. // Strategy is the default logic that applies when creating and updating Pod
  40. // objects via the REST API.
  41. var Strategy = podStrategy{api.Scheme, api.SimpleNameGenerator}
  42. // NamespaceScoped is true for pods.
  43. func (podStrategy) NamespaceScoped() bool {
  44. return true
  45. }
  46. // PrepareForCreate clears fields that are not allowed to be set by end users on creation.
  47. func (podStrategy) PrepareForCreate(ctx api.Context, obj runtime.Object) {
  48. pod := obj.(*api.Pod)
  49. pod.Status = api.PodStatus{
  50. Phase: api.PodPending,
  51. }
  52. }
  53. // PrepareForUpdate clears fields that are not allowed to be set by end users on update.
  54. func (podStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
  55. newPod := obj.(*api.Pod)
  56. oldPod := old.(*api.Pod)
  57. newPod.Status = oldPod.Status
  58. }
  59. // Validate validates a new pod.
  60. func (podStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList {
  61. pod := obj.(*api.Pod)
  62. return validation.ValidatePod(pod)
  63. }
  64. // Canonicalize normalizes the object after validation.
  65. func (podStrategy) Canonicalize(obj runtime.Object) {
  66. }
  67. // AllowCreateOnUpdate is false for pods.
  68. func (podStrategy) AllowCreateOnUpdate() bool {
  69. return false
  70. }
  71. // ValidateUpdate is the default update validation for an end user.
  72. func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
  73. errorList := validation.ValidatePod(obj.(*api.Pod))
  74. return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
  75. }
  76. // AllowUnconditionalUpdate allows pods to be overwritten
  77. func (podStrategy) AllowUnconditionalUpdate() bool {
  78. return true
  79. }
  80. // CheckGracefulDelete allows a pod to be gracefully deleted. It updates the DeleteOptions to
  81. // reflect the desired grace value.
  82. func (podStrategy) CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool {
  83. if options == nil {
  84. return false
  85. }
  86. pod := obj.(*api.Pod)
  87. period := int64(0)
  88. // user has specified a value
  89. if options.GracePeriodSeconds != nil {
  90. period = *options.GracePeriodSeconds
  91. } else {
  92. // use the default value if set, or deletes the pod immediately (0)
  93. if pod.Spec.TerminationGracePeriodSeconds != nil {
  94. period = *pod.Spec.TerminationGracePeriodSeconds
  95. }
  96. }
  97. // if the pod is not scheduled, delete immediately
  98. if len(pod.Spec.NodeName) == 0 {
  99. period = 0
  100. }
  101. // if the pod is already terminated, delete immediately
  102. if pod.Status.Phase == api.PodFailed || pod.Status.Phase == api.PodSucceeded {
  103. period = 0
  104. }
  105. // ensure the options and the pod are in sync
  106. options.GracePeriodSeconds = &period
  107. return true
  108. }
  109. type podStrategyWithoutGraceful struct {
  110. podStrategy
  111. }
  112. // CheckGracefulDelete prohibits graceful deletion.
  113. func (podStrategyWithoutGraceful) CheckGracefulDelete(ctx api.Context, obj runtime.Object, options *api.DeleteOptions) bool {
  114. return false
  115. }
  116. // StrategyWithoutGraceful implements the legacy instant delele behavior.
  117. var StrategyWithoutGraceful = podStrategyWithoutGraceful{Strategy}
  118. type podStatusStrategy struct {
  119. podStrategy
  120. }
  121. var StatusStrategy = podStatusStrategy{Strategy}
  122. func (podStatusStrategy) PrepareForUpdate(ctx api.Context, obj, old runtime.Object) {
  123. newPod := obj.(*api.Pod)
  124. oldPod := old.(*api.Pod)
  125. newPod.Spec = oldPod.Spec
  126. newPod.DeletionTimestamp = nil
  127. }
  128. func (podStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList {
  129. // TODO: merge valid fields after update
  130. return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
  131. }
  132. // MatchPod returns a generic matcher for a given label and field selector.
  133. func MatchPod(label labels.Selector, field fields.Selector) *generic.SelectionPredicate {
  134. return &generic.SelectionPredicate{
  135. Label: label,
  136. Field: field,
  137. GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
  138. pod, ok := obj.(*api.Pod)
  139. if !ok {
  140. return nil, nil, fmt.Errorf("not a pod")
  141. }
  142. // podLabels is already sitting there ready to be used.
  143. // podFields is not available directly and requires allocation of a map.
  144. // Only bother if the fields might be useful to determining the match.
  145. // One common case is for a replication controller to set up a watch
  146. // based on labels alone; in that case we can avoid allocating the field map.
  147. // This is especially important in the apiserver.
  148. podLabels := labels.Set(pod.ObjectMeta.Labels)
  149. var podFields fields.Set
  150. if !field.Empty() && label.Matches(podLabels) {
  151. podFields = PodToSelectableFields(pod)
  152. }
  153. return podLabels, podFields, nil
  154. },
  155. IndexFields: []string{"spec.nodeName"},
  156. }
  157. }
  158. func NodeNameTriggerFunc(obj runtime.Object) []storage.MatchValue {
  159. pod := obj.(*api.Pod)
  160. result := storage.MatchValue{IndexName: "spec.nodeName", Value: pod.Spec.NodeName}
  161. return []storage.MatchValue{result}
  162. }
  163. // PodToSelectableFields returns a field set that represents the object
  164. // TODO: fields are not labels, and the validation rules for them do not apply.
  165. func PodToSelectableFields(pod *api.Pod) fields.Set {
  166. objectMetaFieldsSet := generic.ObjectMetaFieldsSet(&pod.ObjectMeta, true)
  167. podSpecificFieldsSet := fields.Set{
  168. "spec.nodeName": pod.Spec.NodeName,
  169. "spec.restartPolicy": string(pod.Spec.RestartPolicy),
  170. "status.phase": string(pod.Status.Phase),
  171. }
  172. return generic.MergeFieldsSets(objectMetaFieldsSet, podSpecificFieldsSet)
  173. }
  174. // ResourceGetter is an interface for retrieving resources by ResourceLocation.
  175. type ResourceGetter interface {
  176. Get(api.Context, string) (runtime.Object, error)
  177. }
  178. func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) {
  179. obj, err := getter.Get(ctx, name)
  180. if err != nil {
  181. return nil, err
  182. }
  183. pod := obj.(*api.Pod)
  184. if pod == nil {
  185. return nil, fmt.Errorf("Unexpected object type: %#v", pod)
  186. }
  187. return pod, nil
  188. }
  189. // ResourceLocation returns a URL to which one can send traffic for the specified pod.
  190. func ResourceLocation(getter ResourceGetter, rt http.RoundTripper, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
  191. // Allow ID as "podname" or "podname:port" or "scheme:podname:port".
  192. // If port is not specified, try to use the first defined port on the pod.
  193. scheme, name, port, valid := utilnet.SplitSchemeNamePort(id)
  194. if !valid {
  195. return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id))
  196. }
  197. // TODO: if port is not a number but a "(container)/(portname)", do a name lookup.
  198. pod, err := getPod(getter, ctx, name)
  199. if err != nil {
  200. return nil, nil, err
  201. }
  202. // Try to figure out a port.
  203. if port == "" {
  204. for i := range pod.Spec.Containers {
  205. if len(pod.Spec.Containers[i].Ports) > 0 {
  206. port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort)
  207. break
  208. }
  209. }
  210. }
  211. loc := &url.URL{
  212. Scheme: scheme,
  213. }
  214. if port == "" {
  215. loc.Host = pod.Status.PodIP
  216. } else {
  217. loc.Host = net.JoinHostPort(pod.Status.PodIP, port)
  218. }
  219. return loc, rt, nil
  220. }
  221. // getContainerNames returns a formatted string containing the container names
  222. func getContainerNames(containers []api.Container) string {
  223. names := []string{}
  224. for _, c := range containers {
  225. names = append(names, c.Name)
  226. }
  227. return strings.Join(names, " ")
  228. }
  229. // LogLocation returns the log URL for a pod container. If opts.Container is blank
  230. // and only one container is present in the pod, that container is used.
  231. func LogLocation(
  232. getter ResourceGetter,
  233. connInfo client.ConnectionInfoGetter,
  234. ctx api.Context,
  235. name string,
  236. opts *api.PodLogOptions,
  237. ) (*url.URL, http.RoundTripper, error) {
  238. pod, err := getPod(getter, ctx, name)
  239. if err != nil {
  240. return nil, nil, err
  241. }
  242. // Try to figure out a container
  243. // If a container was provided, it must be valid
  244. container := opts.Container
  245. if len(container) == 0 {
  246. switch len(pod.Spec.Containers) {
  247. case 1:
  248. container = pod.Spec.Containers[0].Name
  249. case 0:
  250. return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
  251. default:
  252. containerNames := getContainerNames(pod.Spec.Containers)
  253. initContainerNames := getContainerNames(pod.Spec.InitContainers)
  254. err := fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)
  255. if len(initContainerNames) > 0 {
  256. err += fmt.Sprintf(" or one of the init containers: [%s]", initContainerNames)
  257. }
  258. return nil, nil, errors.NewBadRequest(err)
  259. }
  260. } else {
  261. if !podHasContainerWithName(pod, container) {
  262. return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name))
  263. }
  264. }
  265. nodeHost := pod.Spec.NodeName
  266. if len(nodeHost) == 0 {
  267. // If pod has not been assigned a host, return an empty location
  268. return nil, nil, nil
  269. }
  270. nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
  271. if err != nil {
  272. return nil, nil, err
  273. }
  274. params := url.Values{}
  275. if opts.Follow {
  276. params.Add("follow", "true")
  277. }
  278. if opts.Previous {
  279. params.Add("previous", "true")
  280. }
  281. if opts.Timestamps {
  282. params.Add("timestamps", "true")
  283. }
  284. if opts.SinceSeconds != nil {
  285. params.Add("sinceSeconds", strconv.FormatInt(*opts.SinceSeconds, 10))
  286. }
  287. if opts.SinceTime != nil {
  288. params.Add("sinceTime", opts.SinceTime.Format(time.RFC3339))
  289. }
  290. if opts.TailLines != nil {
  291. params.Add("tailLines", strconv.FormatInt(*opts.TailLines, 10))
  292. }
  293. if opts.LimitBytes != nil {
  294. params.Add("limitBytes", strconv.FormatInt(*opts.LimitBytes, 10))
  295. }
  296. loc := &url.URL{
  297. Scheme: nodeScheme,
  298. Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
  299. Path: fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, pod.Name, container),
  300. RawQuery: params.Encode(),
  301. }
  302. return loc, nodeTransport, nil
  303. }
  304. func podHasContainerWithName(pod *api.Pod, containerName string) bool {
  305. for _, c := range pod.Spec.Containers {
  306. if c.Name == containerName {
  307. return true
  308. }
  309. }
  310. for _, c := range pod.Spec.InitContainers {
  311. if c.Name == containerName {
  312. return true
  313. }
  314. }
  315. return false
  316. }
  317. func streamParams(params url.Values, opts runtime.Object) error {
  318. switch opts := opts.(type) {
  319. case *api.PodExecOptions:
  320. if opts.Stdin {
  321. params.Add(api.ExecStdinParam, "1")
  322. }
  323. if opts.Stdout {
  324. params.Add(api.ExecStdoutParam, "1")
  325. }
  326. if opts.Stderr {
  327. params.Add(api.ExecStderrParam, "1")
  328. }
  329. if opts.TTY {
  330. params.Add(api.ExecTTYParam, "1")
  331. }
  332. for _, c := range opts.Command {
  333. params.Add("command", c)
  334. }
  335. case *api.PodAttachOptions:
  336. if opts.Stdin {
  337. params.Add(api.ExecStdinParam, "1")
  338. }
  339. if opts.Stdout {
  340. params.Add(api.ExecStdoutParam, "1")
  341. }
  342. if opts.Stderr {
  343. params.Add(api.ExecStderrParam, "1")
  344. }
  345. if opts.TTY {
  346. params.Add(api.ExecTTYParam, "1")
  347. }
  348. default:
  349. return fmt.Errorf("Unknown object for streaming: %v", opts)
  350. }
  351. return nil
  352. }
  353. // AttachLocation returns the attach URL for a pod container. If opts.Container is blank
  354. // and only one container is present in the pod, that container is used.
  355. func AttachLocation(
  356. getter ResourceGetter,
  357. connInfo client.ConnectionInfoGetter,
  358. ctx api.Context,
  359. name string,
  360. opts *api.PodAttachOptions,
  361. ) (*url.URL, http.RoundTripper, error) {
  362. return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "attach")
  363. }
  364. // ExecLocation returns the exec URL for a pod container. If opts.Container is blank
  365. // and only one container is present in the pod, that container is used.
  366. func ExecLocation(
  367. getter ResourceGetter,
  368. connInfo client.ConnectionInfoGetter,
  369. ctx api.Context,
  370. name string,
  371. opts *api.PodExecOptions,
  372. ) (*url.URL, http.RoundTripper, error) {
  373. return streamLocation(getter, connInfo, ctx, name, opts, opts.Container, "exec")
  374. }
  375. func streamLocation(
  376. getter ResourceGetter,
  377. connInfo client.ConnectionInfoGetter,
  378. ctx api.Context,
  379. name string,
  380. opts runtime.Object,
  381. container,
  382. path string,
  383. ) (*url.URL, http.RoundTripper, error) {
  384. pod, err := getPod(getter, ctx, name)
  385. if err != nil {
  386. return nil, nil, err
  387. }
  388. // Try to figure out a container
  389. // If a container was provided, it must be valid
  390. if container == "" {
  391. switch len(pod.Spec.Containers) {
  392. case 1:
  393. container = pod.Spec.Containers[0].Name
  394. case 0:
  395. return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
  396. default:
  397. containerNames := getContainerNames(pod.Spec.Containers)
  398. initContainerNames := getContainerNames(pod.Spec.InitContainers)
  399. err := fmt.Sprintf("a container name must be specified for pod %s, choose one of: [%s]", name, containerNames)
  400. if len(initContainerNames) > 0 {
  401. err += fmt.Sprintf(" or one of the init containers: [%s]", initContainerNames)
  402. }
  403. return nil, nil, errors.NewBadRequest(err)
  404. }
  405. } else {
  406. if !podHasContainerWithName(pod, container) {
  407. return nil, nil, errors.NewBadRequest(fmt.Sprintf("container %s is not valid for pod %s", container, name))
  408. }
  409. }
  410. nodeHost := pod.Spec.NodeName
  411. if len(nodeHost) == 0 {
  412. // If pod has not been assigned a host, return an empty location
  413. return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
  414. }
  415. nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
  416. if err != nil {
  417. return nil, nil, err
  418. }
  419. params := url.Values{}
  420. if err := streamParams(params, opts); err != nil {
  421. return nil, nil, err
  422. }
  423. loc := &url.URL{
  424. Scheme: nodeScheme,
  425. Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
  426. Path: fmt.Sprintf("/%s/%s/%s/%s", path, pod.Namespace, pod.Name, container),
  427. RawQuery: params.Encode(),
  428. }
  429. return loc, nodeTransport, nil
  430. }
  431. // PortForwardLocation returns the port-forward URL for a pod.
  432. func PortForwardLocation(
  433. getter ResourceGetter,
  434. connInfo client.ConnectionInfoGetter,
  435. ctx api.Context,
  436. name string,
  437. ) (*url.URL, http.RoundTripper, error) {
  438. pod, err := getPod(getter, ctx, name)
  439. if err != nil {
  440. return nil, nil, err
  441. }
  442. nodeHost := pod.Spec.NodeName
  443. if len(nodeHost) == 0 {
  444. // If pod has not been assigned a host, return an empty location
  445. return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
  446. }
  447. nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(ctx, nodeHost)
  448. if err != nil {
  449. return nil, nil, err
  450. }
  451. loc := &url.URL{
  452. Scheme: nodeScheme,
  453. Host: fmt.Sprintf("%s:%d", nodeHost, nodePort),
  454. Path: fmt.Sprintf("/portForward/%s/%s", pod.Namespace, pod.Name),
  455. }
  456. return loc, nodeTransport, nil
  457. }