daemon_restart.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /*
  2. Copyright 2015 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 apps
  14. import (
  15. "fmt"
  16. "strconv"
  17. "time"
  18. v1 "k8s.io/api/core/v1"
  19. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  20. "k8s.io/apimachinery/pkg/labels"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/util/sets"
  23. "k8s.io/apimachinery/pkg/util/uuid"
  24. "k8s.io/apimachinery/pkg/util/wait"
  25. "k8s.io/apimachinery/pkg/watch"
  26. clientset "k8s.io/client-go/kubernetes"
  27. "k8s.io/client-go/tools/cache"
  28. "k8s.io/kubernetes/pkg/master/ports"
  29. "k8s.io/kubernetes/test/e2e/framework"
  30. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  31. e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
  32. testutils "k8s.io/kubernetes/test/utils"
  33. imageutils "k8s.io/kubernetes/test/utils/image"
  34. "github.com/onsi/ginkgo"
  35. )
  36. // This test primarily checks 2 things:
  37. // 1. Daemons restart automatically within some sane time (10m).
  38. // 2. They don't take abnormal actions when restarted in the steady state.
  39. // - Controller manager shouldn't overshoot replicas
  40. // - Kubelet shouldn't restart containers
  41. // - Scheduler should continue assigning hosts to new pods
  42. const (
  43. restartPollInterval = 5 * time.Second
  44. restartTimeout = 10 * time.Minute
  45. numPods = 10
  46. // ADD represents the ADD event
  47. ADD = "ADD"
  48. // DEL represents the DEL event
  49. DEL = "DEL"
  50. // UPDATE represents the UPDATE event
  51. UPDATE = "UPDATE"
  52. )
  53. // RestartDaemonConfig is a config to restart a running daemon on a node, and wait till
  54. // it comes back up. It uses ssh to send a SIGTERM to the daemon.
  55. type RestartDaemonConfig struct {
  56. nodeName string
  57. daemonName string
  58. healthzPort int
  59. pollInterval time.Duration
  60. pollTimeout time.Duration
  61. }
  62. // NewRestartConfig creates a RestartDaemonConfig for the given node and daemon.
  63. func NewRestartConfig(nodeName, daemonName string, healthzPort int, pollInterval, pollTimeout time.Duration) *RestartDaemonConfig {
  64. if !framework.ProviderIs("gce") {
  65. e2elog.Logf("WARNING: SSH through the restart config might not work on %s", framework.TestContext.Provider)
  66. }
  67. return &RestartDaemonConfig{
  68. nodeName: nodeName,
  69. daemonName: daemonName,
  70. healthzPort: healthzPort,
  71. pollInterval: pollInterval,
  72. pollTimeout: pollTimeout,
  73. }
  74. }
  75. func (r *RestartDaemonConfig) String() string {
  76. return fmt.Sprintf("Daemon %v on node %v", r.daemonName, r.nodeName)
  77. }
  78. // waitUp polls healthz of the daemon till it returns "ok" or the polling hits the pollTimeout
  79. func (r *RestartDaemonConfig) waitUp() {
  80. e2elog.Logf("Checking if %v is up by polling for a 200 on its /healthz endpoint", r)
  81. healthzCheck := fmt.Sprintf(
  82. "curl -s -o /dev/null -I -w \"%%{http_code}\" http://localhost:%v/healthz", r.healthzPort)
  83. err := wait.Poll(r.pollInterval, r.pollTimeout, func() (bool, error) {
  84. result, err := e2essh.NodeExec(r.nodeName, healthzCheck, framework.TestContext.Provider)
  85. framework.ExpectNoError(err)
  86. if result.Code == 0 {
  87. httpCode, err := strconv.Atoi(result.Stdout)
  88. if err != nil {
  89. e2elog.Logf("Unable to parse healthz http return code: %v", err)
  90. } else if httpCode == 200 {
  91. return true, nil
  92. }
  93. }
  94. e2elog.Logf("node %v exec command, '%v' failed with exitcode %v: \n\tstdout: %v\n\tstderr: %v",
  95. r.nodeName, healthzCheck, result.Code, result.Stdout, result.Stderr)
  96. return false, nil
  97. })
  98. framework.ExpectNoError(err, "%v did not respond with a 200 via %v within %v", r, healthzCheck, r.pollTimeout)
  99. }
  100. // kill sends a SIGTERM to the daemon
  101. func (r *RestartDaemonConfig) kill() {
  102. e2elog.Logf("Killing %v", r)
  103. _, err := e2essh.NodeExec(r.nodeName, fmt.Sprintf("pgrep %v | xargs -I {} sudo kill {}", r.daemonName), framework.TestContext.Provider)
  104. framework.ExpectNoError(err)
  105. }
  106. // Restart checks if the daemon is up, kills it, and waits till it comes back up
  107. func (r *RestartDaemonConfig) restart() {
  108. r.waitUp()
  109. r.kill()
  110. r.waitUp()
  111. }
  112. // podTracker records a serial history of events that might've affects pods.
  113. type podTracker struct {
  114. cache.ThreadSafeStore
  115. }
  116. func (p *podTracker) remember(pod *v1.Pod, eventType string) {
  117. if eventType == UPDATE && pod.Status.Phase == v1.PodRunning {
  118. return
  119. }
  120. p.Add(fmt.Sprintf("[%v] %v: %v", time.Now(), eventType, pod.Name), pod)
  121. }
  122. func (p *podTracker) String() (msg string) {
  123. for _, k := range p.ListKeys() {
  124. obj, exists := p.Get(k)
  125. if !exists {
  126. continue
  127. }
  128. pod := obj.(*v1.Pod)
  129. msg += fmt.Sprintf("%v Phase %v Host %v\n", k, pod.Status.Phase, pod.Spec.NodeName)
  130. }
  131. return
  132. }
  133. func newPodTracker() *podTracker {
  134. return &podTracker{cache.NewThreadSafeStore(
  135. cache.Indexers{}, cache.Indices{})}
  136. }
  137. // replacePods replaces content of the store with the given pods.
  138. func replacePods(pods []*v1.Pod, store cache.Store) {
  139. found := make([]interface{}, 0, len(pods))
  140. for i := range pods {
  141. found = append(found, pods[i])
  142. }
  143. framework.ExpectNoError(store.Replace(found, "0"))
  144. }
  145. // getContainerRestarts returns the count of container restarts across all pods matching the given labelSelector,
  146. // and a list of nodenames across which these containers restarted.
  147. func getContainerRestarts(c clientset.Interface, ns string, labelSelector labels.Selector) (int, []string) {
  148. options := metav1.ListOptions{LabelSelector: labelSelector.String()}
  149. pods, err := c.CoreV1().Pods(ns).List(options)
  150. framework.ExpectNoError(err)
  151. failedContainers := 0
  152. containerRestartNodes := sets.NewString()
  153. for _, p := range pods.Items {
  154. for _, v := range testutils.FailedContainers(&p) {
  155. failedContainers = failedContainers + v.Restarts
  156. containerRestartNodes.Insert(p.Spec.NodeName)
  157. }
  158. }
  159. return failedContainers, containerRestartNodes.List()
  160. }
  161. var _ = SIGDescribe("DaemonRestart [Disruptive]", func() {
  162. f := framework.NewDefaultFramework("daemonrestart")
  163. rcName := "daemonrestart" + strconv.Itoa(numPods) + "-" + string(uuid.NewUUID())
  164. labelSelector := labels.Set(map[string]string{"name": rcName}).AsSelector()
  165. existingPods := cache.NewStore(cache.MetaNamespaceKeyFunc)
  166. var ns string
  167. var config testutils.RCConfig
  168. var controller cache.Controller
  169. var newPods cache.Store
  170. var stopCh chan struct{}
  171. var tracker *podTracker
  172. ginkgo.BeforeEach(func() {
  173. // These tests require SSH
  174. framework.SkipUnlessProviderIs(framework.ProvidersWithSSH...)
  175. ns = f.Namespace.Name
  176. // All the restart tests need an rc and a watch on pods of the rc.
  177. // Additionally some of them might scale the rc during the test.
  178. config = testutils.RCConfig{
  179. Client: f.ClientSet,
  180. Name: rcName,
  181. Namespace: ns,
  182. Image: imageutils.GetPauseImageName(),
  183. Replicas: numPods,
  184. CreatedPods: &[]*v1.Pod{},
  185. }
  186. framework.ExpectNoError(framework.RunRC(config))
  187. replacePods(*config.CreatedPods, existingPods)
  188. stopCh = make(chan struct{})
  189. tracker = newPodTracker()
  190. newPods, controller = cache.NewInformer(
  191. &cache.ListWatch{
  192. ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
  193. options.LabelSelector = labelSelector.String()
  194. obj, err := f.ClientSet.CoreV1().Pods(ns).List(options)
  195. return runtime.Object(obj), err
  196. },
  197. WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
  198. options.LabelSelector = labelSelector.String()
  199. return f.ClientSet.CoreV1().Pods(ns).Watch(options)
  200. },
  201. },
  202. &v1.Pod{},
  203. 0,
  204. cache.ResourceEventHandlerFuncs{
  205. AddFunc: func(obj interface{}) {
  206. tracker.remember(obj.(*v1.Pod), ADD)
  207. },
  208. UpdateFunc: func(oldObj, newObj interface{}) {
  209. tracker.remember(newObj.(*v1.Pod), UPDATE)
  210. },
  211. DeleteFunc: func(obj interface{}) {
  212. tracker.remember(obj.(*v1.Pod), DEL)
  213. },
  214. },
  215. )
  216. go controller.Run(stopCh)
  217. })
  218. ginkgo.AfterEach(func() {
  219. close(stopCh)
  220. })
  221. ginkgo.It("Controller Manager should not create/delete replicas across restart", func() {
  222. // Requires master ssh access.
  223. framework.SkipUnlessProviderIs("gce", "aws")
  224. restarter := NewRestartConfig(
  225. framework.GetMasterHost(), "kube-controller", ports.InsecureKubeControllerManagerPort, restartPollInterval, restartTimeout)
  226. restarter.restart()
  227. // The intent is to ensure the replication controller manager has observed and reported status of
  228. // the replication controller at least once since the manager restarted, so that we can determine
  229. // that it had the opportunity to create/delete pods, if it were going to do so. Scaling the RC
  230. // to the same size achieves this, because the scale operation advances the RC's sequence number
  231. // and awaits it to be observed and reported back in the RC's status.
  232. framework.ScaleRC(f.ClientSet, f.ScalesGetter, ns, rcName, numPods, true)
  233. // Only check the keys, the pods can be different if the kubelet updated it.
  234. // TODO: Can it really?
  235. existingKeys := sets.NewString()
  236. newKeys := sets.NewString()
  237. for _, k := range existingPods.ListKeys() {
  238. existingKeys.Insert(k)
  239. }
  240. for _, k := range newPods.ListKeys() {
  241. newKeys.Insert(k)
  242. }
  243. if len(newKeys.List()) != len(existingKeys.List()) ||
  244. !newKeys.IsSuperset(existingKeys) {
  245. framework.Failf("RcManager created/deleted pods after restart \n\n %+v", tracker)
  246. }
  247. })
  248. ginkgo.It("Scheduler should continue assigning pods to nodes across restart", func() {
  249. // Requires master ssh access.
  250. framework.SkipUnlessProviderIs("gce", "aws")
  251. restarter := NewRestartConfig(
  252. framework.GetMasterHost(), "kube-scheduler", ports.InsecureSchedulerPort, restartPollInterval, restartTimeout)
  253. // Create pods while the scheduler is down and make sure the scheduler picks them up by
  254. // scaling the rc to the same size.
  255. restarter.waitUp()
  256. restarter.kill()
  257. // This is best effort to try and create pods while the scheduler is down,
  258. // since we don't know exactly when it is restarted after the kill signal.
  259. framework.ExpectNoError(framework.ScaleRC(f.ClientSet, f.ScalesGetter, ns, rcName, numPods+5, false))
  260. restarter.waitUp()
  261. framework.ExpectNoError(framework.ScaleRC(f.ClientSet, f.ScalesGetter, ns, rcName, numPods+5, true))
  262. })
  263. ginkgo.It("Kubelet should not restart containers across restart", func() {
  264. nodeIPs, err := framework.GetNodePublicIps(f.ClientSet)
  265. framework.ExpectNoError(err)
  266. preRestarts, badNodes := getContainerRestarts(f.ClientSet, ns, labelSelector)
  267. if preRestarts != 0 {
  268. e2elog.Logf("WARNING: Non-zero container restart count: %d across nodes %v", preRestarts, badNodes)
  269. }
  270. for _, ip := range nodeIPs {
  271. restarter := NewRestartConfig(
  272. ip, "kubelet", ports.KubeletReadOnlyPort, restartPollInterval, restartTimeout)
  273. restarter.restart()
  274. }
  275. postRestarts, badNodes := getContainerRestarts(f.ClientSet, ns, labelSelector)
  276. if postRestarts != preRestarts {
  277. framework.DumpNodeDebugInfo(f.ClientSet, badNodes, e2elog.Logf)
  278. framework.Failf("Net container restart count went from %v -> %v after kubelet restart on nodes %v \n\n %+v", preRestarts, postRestarts, badNodes, tracker)
  279. }
  280. })
  281. })