status_manager.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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 status
  14. import (
  15. "sort"
  16. "sync"
  17. "time"
  18. clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
  19. "github.com/golang/glog"
  20. "k8s.io/kubernetes/pkg/api"
  21. "k8s.io/kubernetes/pkg/api/errors"
  22. "k8s.io/kubernetes/pkg/api/unversioned"
  23. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  24. kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
  25. kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
  26. "k8s.io/kubernetes/pkg/kubelet/util/format"
  27. "k8s.io/kubernetes/pkg/types"
  28. "k8s.io/kubernetes/pkg/util/diff"
  29. "k8s.io/kubernetes/pkg/util/wait"
  30. )
  31. // A wrapper around api.PodStatus that includes a version to enforce that stale pod statuses are
  32. // not sent to the API server.
  33. type versionedPodStatus struct {
  34. status api.PodStatus
  35. // Monotonically increasing version number (per pod).
  36. version uint64
  37. // Pod name & namespace, for sending updates to API server.
  38. podName string
  39. podNamespace string
  40. }
  41. type podStatusSyncRequest struct {
  42. podUID types.UID
  43. status versionedPodStatus
  44. }
  45. // Updates pod statuses in apiserver. Writes only when new status has changed.
  46. // All methods are thread-safe.
  47. type manager struct {
  48. kubeClient clientset.Interface
  49. podManager kubepod.Manager
  50. // Map from pod UID to sync status of the corresponding pod.
  51. podStatuses map[types.UID]versionedPodStatus
  52. podStatusesLock sync.RWMutex
  53. podStatusChannel chan podStatusSyncRequest
  54. // Map from (mirror) pod UID to latest status version successfully sent to the API server.
  55. // apiStatusVersions must only be accessed from the sync thread.
  56. apiStatusVersions map[types.UID]uint64
  57. }
  58. // PodStatusProvider knows how to provide status for a pod. It's intended to be used by other components
  59. // that need to introspect status.
  60. type PodStatusProvider interface {
  61. // GetPodStatus returns the cached status for the provided pod UID, as well as whether it
  62. // was a cache hit.
  63. GetPodStatus(uid types.UID) (api.PodStatus, bool)
  64. }
  65. // Manager is the Source of truth for kubelet pod status, and should be kept up-to-date with
  66. // the latest api.PodStatus. It also syncs updates back to the API server.
  67. type Manager interface {
  68. PodStatusProvider
  69. // Start the API server status sync loop.
  70. Start()
  71. // SetPodStatus caches updates the cached status for the given pod, and triggers a status update.
  72. SetPodStatus(pod *api.Pod, status api.PodStatus)
  73. // SetContainerReadiness updates the cached container status with the given readiness, and
  74. // triggers a status update.
  75. SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool)
  76. // TerminatePod resets the container status for the provided pod to terminated and triggers
  77. // a status update.
  78. TerminatePod(pod *api.Pod)
  79. // RemoveOrphanedStatuses scans the status cache and removes any entries for pods not included in
  80. // the provided podUIDs.
  81. RemoveOrphanedStatuses(podUIDs map[types.UID]bool)
  82. }
  83. const syncPeriod = 10 * time.Second
  84. func NewManager(kubeClient clientset.Interface, podManager kubepod.Manager) Manager {
  85. return &manager{
  86. kubeClient: kubeClient,
  87. podManager: podManager,
  88. podStatuses: make(map[types.UID]versionedPodStatus),
  89. podStatusChannel: make(chan podStatusSyncRequest, 1000), // Buffer up to 1000 statuses
  90. apiStatusVersions: make(map[types.UID]uint64),
  91. }
  92. }
  93. // isStatusEqual returns true if the given pod statuses are equal, false otherwise.
  94. // This method normalizes the status before comparing so as to make sure that meaningless
  95. // changes will be ignored.
  96. func isStatusEqual(oldStatus, status *api.PodStatus) bool {
  97. return api.Semantic.DeepEqual(status, oldStatus)
  98. }
  99. func (m *manager) Start() {
  100. // Don't start the status manager if we don't have a client. This will happen
  101. // on the master, where the kubelet is responsible for bootstrapping the pods
  102. // of the master components.
  103. if m.kubeClient == nil {
  104. glog.Infof("Kubernetes client is nil, not starting status manager.")
  105. return
  106. }
  107. glog.Info("Starting to sync pod status with apiserver")
  108. syncTicker := time.Tick(syncPeriod)
  109. // syncPod and syncBatch share the same go routine to avoid sync races.
  110. go wait.Forever(func() {
  111. select {
  112. case syncRequest := <-m.podStatusChannel:
  113. m.syncPod(syncRequest.podUID, syncRequest.status)
  114. case <-syncTicker:
  115. m.syncBatch()
  116. }
  117. }, 0)
  118. }
  119. func (m *manager) GetPodStatus(uid types.UID) (api.PodStatus, bool) {
  120. m.podStatusesLock.RLock()
  121. defer m.podStatusesLock.RUnlock()
  122. status, ok := m.podStatuses[m.podManager.TranslatePodUID(uid)]
  123. return status.status, ok
  124. }
  125. func (m *manager) SetPodStatus(pod *api.Pod, status api.PodStatus) {
  126. m.podStatusesLock.Lock()
  127. defer m.podStatusesLock.Unlock()
  128. // Make sure we're caching a deep copy.
  129. status, err := copyStatus(&status)
  130. if err != nil {
  131. return
  132. }
  133. // Force a status update if deletion timestamp is set. This is necessary
  134. // because if the pod is in the non-running state, the pod worker still
  135. // needs to be able to trigger an update and/or deletion.
  136. m.updateStatusInternal(pod, status, pod.DeletionTimestamp != nil)
  137. }
  138. func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) {
  139. m.podStatusesLock.Lock()
  140. defer m.podStatusesLock.Unlock()
  141. pod, ok := m.podManager.GetPodByUID(podUID)
  142. if !ok {
  143. glog.V(4).Infof("Pod %q has been deleted, no need to update readiness", string(podUID))
  144. return
  145. }
  146. oldStatus, found := m.podStatuses[pod.UID]
  147. if !found {
  148. glog.Warningf("Container readiness changed before pod has synced: %q - %q",
  149. format.Pod(pod), containerID.String())
  150. return
  151. }
  152. // Find the container to update.
  153. containerStatus, _, ok := findContainerStatus(&oldStatus.status, containerID.String())
  154. if !ok {
  155. glog.Warningf("Container readiness changed for unknown container: %q - %q",
  156. format.Pod(pod), containerID.String())
  157. return
  158. }
  159. if containerStatus.Ready == ready {
  160. glog.V(4).Infof("Container readiness unchanged (%v): %q - %q", ready,
  161. format.Pod(pod), containerID.String())
  162. return
  163. }
  164. // Make sure we're not updating the cached version.
  165. status, err := copyStatus(&oldStatus.status)
  166. if err != nil {
  167. return
  168. }
  169. containerStatus, _, _ = findContainerStatus(&status, containerID.String())
  170. containerStatus.Ready = ready
  171. // Update pod condition.
  172. readyConditionIndex := -1
  173. for i, condition := range status.Conditions {
  174. if condition.Type == api.PodReady {
  175. readyConditionIndex = i
  176. break
  177. }
  178. }
  179. readyCondition := GeneratePodReadyCondition(&pod.Spec, status.ContainerStatuses, status.Phase)
  180. if readyConditionIndex != -1 {
  181. status.Conditions[readyConditionIndex] = readyCondition
  182. } else {
  183. glog.Warningf("PodStatus missing PodReady condition: %+v", status)
  184. status.Conditions = append(status.Conditions, readyCondition)
  185. }
  186. m.updateStatusInternal(pod, status, false)
  187. }
  188. func findContainerStatus(status *api.PodStatus, containerID string) (containerStatus *api.ContainerStatus, init bool, ok bool) {
  189. // Find the container to update.
  190. for i, c := range status.ContainerStatuses {
  191. if c.ContainerID == containerID {
  192. return &status.ContainerStatuses[i], false, true
  193. }
  194. }
  195. for i, c := range status.InitContainerStatuses {
  196. if c.ContainerID == containerID {
  197. return &status.InitContainerStatuses[i], true, true
  198. }
  199. }
  200. return nil, false, false
  201. }
  202. func (m *manager) TerminatePod(pod *api.Pod) {
  203. m.podStatusesLock.Lock()
  204. defer m.podStatusesLock.Unlock()
  205. oldStatus := &pod.Status
  206. if cachedStatus, ok := m.podStatuses[pod.UID]; ok {
  207. oldStatus = &cachedStatus.status
  208. }
  209. status, err := copyStatus(oldStatus)
  210. if err != nil {
  211. return
  212. }
  213. for i := range status.ContainerStatuses {
  214. status.ContainerStatuses[i].State = api.ContainerState{
  215. Terminated: &api.ContainerStateTerminated{},
  216. }
  217. }
  218. for i := range status.InitContainerStatuses {
  219. status.InitContainerStatuses[i].State = api.ContainerState{
  220. Terminated: &api.ContainerStateTerminated{},
  221. }
  222. }
  223. m.updateStatusInternal(pod, pod.Status, true)
  224. }
  225. // updateStatusInternal updates the internal status cache, and queues an update to the api server if
  226. // necessary. Returns whether an update was triggered.
  227. // This method IS NOT THREAD SAFE and must be called from a locked function.
  228. func (m *manager) updateStatusInternal(pod *api.Pod, status api.PodStatus, forceUpdate bool) bool {
  229. var oldStatus api.PodStatus
  230. cachedStatus, isCached := m.podStatuses[pod.UID]
  231. if isCached {
  232. oldStatus = cachedStatus.status
  233. } else if mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod); ok {
  234. oldStatus = mirrorPod.Status
  235. } else {
  236. oldStatus = pod.Status
  237. }
  238. // Set ReadyCondition.LastTransitionTime.
  239. if _, readyCondition := api.GetPodCondition(&status, api.PodReady); readyCondition != nil {
  240. // Need to set LastTransitionTime.
  241. lastTransitionTime := unversioned.Now()
  242. _, oldReadyCondition := api.GetPodCondition(&oldStatus, api.PodReady)
  243. if oldReadyCondition != nil && readyCondition.Status == oldReadyCondition.Status {
  244. lastTransitionTime = oldReadyCondition.LastTransitionTime
  245. }
  246. readyCondition.LastTransitionTime = lastTransitionTime
  247. }
  248. // Set InitializedCondition.LastTransitionTime.
  249. if _, initCondition := api.GetPodCondition(&status, api.PodInitialized); initCondition != nil {
  250. // Need to set LastTransitionTime.
  251. lastTransitionTime := unversioned.Now()
  252. _, oldInitCondition := api.GetPodCondition(&oldStatus, api.PodInitialized)
  253. if oldInitCondition != nil && initCondition.Status == oldInitCondition.Status {
  254. lastTransitionTime = oldInitCondition.LastTransitionTime
  255. }
  256. initCondition.LastTransitionTime = lastTransitionTime
  257. }
  258. // ensure that the start time does not change across updates.
  259. if oldStatus.StartTime != nil && !oldStatus.StartTime.IsZero() {
  260. status.StartTime = oldStatus.StartTime
  261. } else if status.StartTime.IsZero() {
  262. // if the status has no start time, we need to set an initial time
  263. now := unversioned.Now()
  264. status.StartTime = &now
  265. }
  266. normalizeStatus(pod, &status)
  267. // The intent here is to prevent concurrent updates to a pod's status from
  268. // clobbering each other so the phase of a pod progresses monotonically.
  269. if isCached && isStatusEqual(&cachedStatus.status, &status) && !forceUpdate {
  270. glog.V(3).Infof("Ignoring same status for pod %q, status: %+v", format.Pod(pod), status)
  271. return false // No new status.
  272. }
  273. newStatus := versionedPodStatus{
  274. status: status,
  275. version: cachedStatus.version + 1,
  276. podName: pod.Name,
  277. podNamespace: pod.Namespace,
  278. }
  279. m.podStatuses[pod.UID] = newStatus
  280. select {
  281. case m.podStatusChannel <- podStatusSyncRequest{pod.UID, newStatus}:
  282. return true
  283. default:
  284. // Let the periodic syncBatch handle the update if the channel is full.
  285. // We can't block, since we hold the mutex lock.
  286. glog.V(4).Infof("Skpping the status update for pod %q for now because the channel is full; status: %+v",
  287. format.Pod(pod), status)
  288. return false
  289. }
  290. }
  291. // deletePodStatus simply removes the given pod from the status cache.
  292. func (m *manager) deletePodStatus(uid types.UID) {
  293. m.podStatusesLock.Lock()
  294. defer m.podStatusesLock.Unlock()
  295. delete(m.podStatuses, uid)
  296. }
  297. // TODO(filipg): It'd be cleaner if we can do this without signal from user.
  298. func (m *manager) RemoveOrphanedStatuses(podUIDs map[types.UID]bool) {
  299. m.podStatusesLock.Lock()
  300. defer m.podStatusesLock.Unlock()
  301. for key := range m.podStatuses {
  302. if _, ok := podUIDs[key]; !ok {
  303. glog.V(5).Infof("Removing %q from status map.", key)
  304. delete(m.podStatuses, key)
  305. }
  306. }
  307. }
  308. // syncBatch syncs pods statuses with the apiserver.
  309. func (m *manager) syncBatch() {
  310. var updatedStatuses []podStatusSyncRequest
  311. podToMirror, mirrorToPod := m.podManager.GetUIDTranslations()
  312. func() { // Critical section
  313. m.podStatusesLock.RLock()
  314. defer m.podStatusesLock.RUnlock()
  315. // Clean up orphaned versions.
  316. for uid := range m.apiStatusVersions {
  317. _, hasPod := m.podStatuses[uid]
  318. _, hasMirror := mirrorToPod[uid]
  319. if !hasPod && !hasMirror {
  320. delete(m.apiStatusVersions, uid)
  321. }
  322. }
  323. for uid, status := range m.podStatuses {
  324. syncedUID := uid
  325. if mirrorUID, ok := podToMirror[uid]; ok {
  326. syncedUID = mirrorUID
  327. }
  328. if m.needsUpdate(syncedUID, status) {
  329. updatedStatuses = append(updatedStatuses, podStatusSyncRequest{uid, status})
  330. } else if m.needsReconcile(uid, status.status) {
  331. // Delete the apiStatusVersions here to force an update on the pod status
  332. // In most cases the deleted apiStatusVersions here should be filled
  333. // soon after the following syncPod() [If the syncPod() sync an update
  334. // successfully].
  335. delete(m.apiStatusVersions, syncedUID)
  336. updatedStatuses = append(updatedStatuses, podStatusSyncRequest{uid, status})
  337. }
  338. }
  339. }()
  340. for _, update := range updatedStatuses {
  341. m.syncPod(update.podUID, update.status)
  342. }
  343. }
  344. // syncPod syncs the given status with the API server. The caller must not hold the lock.
  345. func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
  346. if !m.needsUpdate(uid, status) {
  347. glog.V(1).Infof("Status for pod %q is up-to-date; skipping", uid)
  348. return
  349. }
  350. // TODO: make me easier to express from client code
  351. pod, err := m.kubeClient.Core().Pods(status.podNamespace).Get(status.podName)
  352. if errors.IsNotFound(err) {
  353. glog.V(3).Infof("Pod %q (%s) does not exist on the server", status.podName, uid)
  354. // If the Pod is deleted the status will be cleared in
  355. // RemoveOrphanedStatuses, so we just ignore the update here.
  356. return
  357. }
  358. if err == nil {
  359. translatedUID := m.podManager.TranslatePodUID(pod.UID)
  360. if len(translatedUID) > 0 && translatedUID != uid {
  361. glog.V(3).Infof("Pod %q was deleted and then recreated, skipping status update", format.Pod(pod))
  362. m.deletePodStatus(uid)
  363. return
  364. }
  365. pod.Status = status.status
  366. // TODO: handle conflict as a retry, make that easier too.
  367. pod, err = m.kubeClient.Core().Pods(pod.Namespace).UpdateStatus(pod)
  368. if err == nil {
  369. glog.V(3).Infof("Status for pod %q updated successfully: %+v", format.Pod(pod), status)
  370. m.apiStatusVersions[pod.UID] = status.version
  371. if kubepod.IsMirrorPod(pod) {
  372. // We don't handle graceful deletion of mirror pods.
  373. return
  374. }
  375. if pod.DeletionTimestamp == nil {
  376. return
  377. }
  378. if !notRunning(pod.Status.ContainerStatuses) {
  379. glog.V(3).Infof("Pod %q is terminated, but some containers are still running", format.Pod(pod))
  380. return
  381. }
  382. deleteOptions := api.NewDeleteOptions(0)
  383. // Use the pod UID as the precondition for deletion to prevent deleting a newly created pod with the same name and namespace.
  384. deleteOptions.Preconditions = api.NewUIDPreconditions(string(pod.UID))
  385. if err = m.kubeClient.Core().Pods(pod.Namespace).Delete(pod.Name, deleteOptions); err == nil {
  386. glog.V(3).Infof("Pod %q fully terminated and removed from etcd", format.Pod(pod))
  387. m.deletePodStatus(uid)
  388. return
  389. }
  390. }
  391. }
  392. // We failed to update status, wait for periodic sync to retry.
  393. glog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err)
  394. }
  395. // needsUpdate returns whether the status is stale for the given pod UID.
  396. // This method is not thread safe, and most only be accessed by the sync thread.
  397. func (m *manager) needsUpdate(uid types.UID, status versionedPodStatus) bool {
  398. latest, ok := m.apiStatusVersions[uid]
  399. return !ok || latest < status.version
  400. }
  401. // needsReconcile compares the given status with the status in the pod manager (which
  402. // in fact comes from apiserver), returns whether the status needs to be reconciled with
  403. // the apiserver. Now when pod status is inconsistent between apiserver and kubelet,
  404. // kubelet should forcibly send an update to reconclie the inconsistence, because kubelet
  405. // should be the source of truth of pod status.
  406. // NOTE(random-liu): It's simpler to pass in mirror pod uid and get mirror pod by uid, but
  407. // now the pod manager only supports getting mirror pod by static pod, so we have to pass
  408. // static pod uid here.
  409. // TODO(random-liu): Simplify the logic when mirror pod manager is added.
  410. func (m *manager) needsReconcile(uid types.UID, status api.PodStatus) bool {
  411. // The pod could be a static pod, so we should translate first.
  412. pod, ok := m.podManager.GetPodByUID(uid)
  413. if !ok {
  414. glog.V(4).Infof("Pod %q has been deleted, no need to reconcile", string(uid))
  415. return false
  416. }
  417. // If the pod is a static pod, we should check its mirror pod, because only status in mirror pod is meaningful to us.
  418. if kubepod.IsStaticPod(pod) {
  419. mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod)
  420. if !ok {
  421. glog.V(4).Infof("Static pod %q has no corresponding mirror pod, no need to reconcile", format.Pod(pod))
  422. return false
  423. }
  424. pod = mirrorPod
  425. }
  426. podStatus, err := copyStatus(&pod.Status)
  427. if err != nil {
  428. return false
  429. }
  430. normalizeStatus(pod, &podStatus)
  431. if isStatusEqual(&podStatus, &status) {
  432. // If the status from the source is the same with the cached status,
  433. // reconcile is not needed. Just return.
  434. return false
  435. }
  436. glog.V(3).Infof("Pod status is inconsistent with cached status for pod %q, a reconciliation should be triggered:\n %+v", format.Pod(pod),
  437. diff.ObjectDiff(podStatus, status))
  438. return true
  439. }
  440. // We add this function, because apiserver only supports *RFC3339* now, which means that the timestamp returned by
  441. // apiserver has no nanosecond information. However, the timestamp returned by unversioned.Now() contains nanosecond,
  442. // so when we do comparison between status from apiserver and cached status, isStatusEqual() will always return false.
  443. // There is related issue #15262 and PR #15263 about this.
  444. // In fact, the best way to solve this is to do it on api side. However, for now, we normalize the status locally in
  445. // kubelet temporarily.
  446. // TODO(random-liu): Remove timestamp related logic after apiserver supports nanosecond or makes it consistent.
  447. func normalizeStatus(pod *api.Pod, status *api.PodStatus) *api.PodStatus {
  448. normalizeTimeStamp := func(t *unversioned.Time) {
  449. *t = t.Rfc3339Copy()
  450. }
  451. normalizeContainerState := func(c *api.ContainerState) {
  452. if c.Running != nil {
  453. normalizeTimeStamp(&c.Running.StartedAt)
  454. }
  455. if c.Terminated != nil {
  456. normalizeTimeStamp(&c.Terminated.StartedAt)
  457. normalizeTimeStamp(&c.Terminated.FinishedAt)
  458. }
  459. }
  460. if status.StartTime != nil {
  461. normalizeTimeStamp(status.StartTime)
  462. }
  463. for i := range status.Conditions {
  464. condition := &status.Conditions[i]
  465. normalizeTimeStamp(&condition.LastProbeTime)
  466. normalizeTimeStamp(&condition.LastTransitionTime)
  467. }
  468. // update container statuses
  469. for i := range status.ContainerStatuses {
  470. cstatus := &status.ContainerStatuses[i]
  471. normalizeContainerState(&cstatus.State)
  472. normalizeContainerState(&cstatus.LastTerminationState)
  473. }
  474. // Sort the container statuses, so that the order won't affect the result of comparison
  475. sort.Sort(kubetypes.SortedContainerStatuses(status.ContainerStatuses))
  476. // update init container statuses
  477. for i := range status.InitContainerStatuses {
  478. cstatus := &status.InitContainerStatuses[i]
  479. normalizeContainerState(&cstatus.State)
  480. normalizeContainerState(&cstatus.LastTerminationState)
  481. }
  482. // Sort the container statuses, so that the order won't affect the result of comparison
  483. kubetypes.SortInitContainerStatuses(pod, status.InitContainerStatuses)
  484. return status
  485. }
  486. // notRunning returns true if every status is terminated or waiting, or the status list
  487. // is empty.
  488. func notRunning(statuses []api.ContainerStatus) bool {
  489. for _, status := range statuses {
  490. if status.State.Terminated == nil && status.State.Waiting == nil {
  491. return false
  492. }
  493. }
  494. return true
  495. }
  496. func copyStatus(source *api.PodStatus) (api.PodStatus, error) {
  497. clone, err := api.Scheme.DeepCopy(source)
  498. if err != nil {
  499. glog.Errorf("Failed to clone status %+v: %v", source, err)
  500. return api.PodStatus{}, err
  501. }
  502. status := *clone.(*api.PodStatus)
  503. return status, nil
  504. }