rc_util.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /*
  2. Copyright 2017 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 framework
  14. import (
  15. "fmt"
  16. "strings"
  17. "time"
  18. "github.com/onsi/ginkgo"
  19. "k8s.io/api/core/v1"
  20. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  21. "k8s.io/apimachinery/pkg/labels"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. clientset "k8s.io/client-go/kubernetes"
  24. scaleclient "k8s.io/client-go/scale"
  25. api "k8s.io/kubernetes/pkg/apis/core"
  26. e2elog "k8s.io/kubernetes/test/e2e/framework/log"
  27. testutils "k8s.io/kubernetes/test/utils"
  28. )
  29. // RcByNamePort returns a ReplicationController with specified name and port
  30. func RcByNamePort(name string, replicas int32, image string, port int, protocol v1.Protocol,
  31. labels map[string]string, gracePeriod *int64) *v1.ReplicationController {
  32. return RcByNameContainer(name, replicas, image, labels, v1.Container{
  33. Name: name,
  34. Image: image,
  35. Ports: []v1.ContainerPort{{ContainerPort: int32(port), Protocol: protocol}},
  36. }, gracePeriod)
  37. }
  38. // RcByNameContainer returns a ReplicationController with specified name and container
  39. func RcByNameContainer(name string, replicas int32, image string, labels map[string]string, c v1.Container,
  40. gracePeriod *int64) *v1.ReplicationController {
  41. zeroGracePeriod := int64(0)
  42. // Add "name": name to the labels, overwriting if it exists.
  43. labels["name"] = name
  44. if gracePeriod == nil {
  45. gracePeriod = &zeroGracePeriod
  46. }
  47. return &v1.ReplicationController{
  48. TypeMeta: metav1.TypeMeta{
  49. Kind: "ReplicationController",
  50. APIVersion: "v1",
  51. },
  52. ObjectMeta: metav1.ObjectMeta{
  53. Name: name,
  54. },
  55. Spec: v1.ReplicationControllerSpec{
  56. Replicas: func(i int32) *int32 { return &i }(replicas),
  57. Selector: map[string]string{
  58. "name": name,
  59. },
  60. Template: &v1.PodTemplateSpec{
  61. ObjectMeta: metav1.ObjectMeta{
  62. Labels: labels,
  63. },
  64. Spec: v1.PodSpec{
  65. Containers: []v1.Container{c},
  66. TerminationGracePeriodSeconds: gracePeriod,
  67. },
  68. },
  69. },
  70. }
  71. }
  72. type updateRcFunc func(d *v1.ReplicationController)
  73. // UpdateReplicationControllerWithRetries retries updating the given rc on conflict with the following steps:
  74. // 1. Get latest resource
  75. // 2. applyUpdate
  76. // 3. Update the resource
  77. func UpdateReplicationControllerWithRetries(c clientset.Interface, namespace, name string, applyUpdate updateRcFunc) (*v1.ReplicationController, error) {
  78. var rc *v1.ReplicationController
  79. var updateErr error
  80. pollErr := wait.PollImmediate(10*time.Millisecond, 1*time.Minute, func() (bool, error) {
  81. var err error
  82. if rc, err = c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{}); err != nil {
  83. return false, err
  84. }
  85. // Apply the update, then attempt to push it to the apiserver.
  86. applyUpdate(rc)
  87. if rc, err = c.CoreV1().ReplicationControllers(namespace).Update(rc); err == nil {
  88. e2elog.Logf("Updating replication controller %q", name)
  89. return true, nil
  90. }
  91. updateErr = err
  92. return false, nil
  93. })
  94. if pollErr == wait.ErrWaitTimeout {
  95. pollErr = fmt.Errorf("couldn't apply the provided updated to rc %q: %v", name, updateErr)
  96. }
  97. return rc, pollErr
  98. }
  99. // DeleteRCAndWaitForGC deletes only the Replication Controller and waits for GC to delete the pods.
  100. func DeleteRCAndWaitForGC(c clientset.Interface, ns, name string) error {
  101. return DeleteResourceAndWaitForGC(c, api.Kind("ReplicationController"), ns, name)
  102. }
  103. // ScaleRC scales Replication Controller to be desired size.
  104. func ScaleRC(clientset clientset.Interface, scalesGetter scaleclient.ScalesGetter, ns, name string, size uint, wait bool) error {
  105. return ScaleResource(clientset, scalesGetter, ns, name, size, wait, api.Kind("ReplicationController"), api.Resource("replicationcontrollers"))
  106. }
  107. // RunRC Launches (and verifies correctness) of a Replication Controller
  108. // and will wait for all pods it spawns to become "Running".
  109. func RunRC(config testutils.RCConfig) error {
  110. ginkgo.By(fmt.Sprintf("creating replication controller %s in namespace %s", config.Name, config.Namespace))
  111. config.NodeDumpFunc = DumpNodeDebugInfo
  112. config.ContainerDumpFunc = LogFailedContainers
  113. return testutils.RunRC(config)
  114. }
  115. // WaitForRCPodToDisappear returns nil if the pod from the given replication controller (described by rcName) no longer exists.
  116. // In case of failure or too long waiting time, an error is returned.
  117. func WaitForRCPodToDisappear(c clientset.Interface, ns, rcName, podName string) error {
  118. label := labels.SelectorFromSet(labels.Set(map[string]string{"name": rcName}))
  119. // NodeController evicts pod after 5 minutes, so we need timeout greater than that to observe effects.
  120. // The grace period must be set to 0 on the pod for it to be deleted during the partition.
  121. // Otherwise, it goes to the 'Terminating' state till the kubelet confirms deletion.
  122. return WaitForPodToDisappear(c, ns, podName, label, 20*time.Second, 10*time.Minute)
  123. }
  124. // WaitForReplicationController waits until the RC appears (exist == true), or disappears (exist == false)
  125. func WaitForReplicationController(c clientset.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error {
  126. err := wait.PollImmediate(interval, timeout, func() (bool, error) {
  127. _, err := c.CoreV1().ReplicationControllers(namespace).Get(name, metav1.GetOptions{})
  128. if err != nil {
  129. e2elog.Logf("Get ReplicationController %s in namespace %s failed (%v).", name, namespace, err)
  130. return !exist, nil
  131. }
  132. e2elog.Logf("ReplicationController %s in namespace %s found.", name, namespace)
  133. return exist, nil
  134. })
  135. if err != nil {
  136. stateMsg := map[bool]string{true: "to appear", false: "to disappear"}
  137. return fmt.Errorf("error waiting for ReplicationController %s/%s %s: %v", namespace, name, stateMsg[exist], err)
  138. }
  139. return nil
  140. }
  141. // WaitForReplicationControllerwithSelector waits until any RC with given selector appears (exist == true), or disappears (exist == false)
  142. func WaitForReplicationControllerwithSelector(c clientset.Interface, namespace string, selector labels.Selector, exist bool, interval,
  143. timeout time.Duration) error {
  144. err := wait.PollImmediate(interval, timeout, func() (bool, error) {
  145. rcs, err := c.CoreV1().ReplicationControllers(namespace).List(metav1.ListOptions{LabelSelector: selector.String()})
  146. switch {
  147. case len(rcs.Items) != 0:
  148. e2elog.Logf("ReplicationController with %s in namespace %s found.", selector.String(), namespace)
  149. return exist, nil
  150. case len(rcs.Items) == 0:
  151. e2elog.Logf("ReplicationController with %s in namespace %s disappeared.", selector.String(), namespace)
  152. return !exist, nil
  153. default:
  154. e2elog.Logf("List ReplicationController with %s in namespace %s failed: %v", selector.String(), namespace, err)
  155. return false, nil
  156. }
  157. })
  158. if err != nil {
  159. stateMsg := map[bool]string{true: "to appear", false: "to disappear"}
  160. return fmt.Errorf("error waiting for ReplicationControllers with %s in namespace %s %s: %v", selector.String(), namespace, stateMsg[exist], err)
  161. }
  162. return nil
  163. }
  164. // trimDockerRegistry is the function for trimming the docker.io/library from the beginning of the imagename.
  165. // If community docker installed it will not prefix the registry names with the dockerimages vs registry names prefixed with other runtimes or docker installed via RHEL extra repo.
  166. // So this function will help to trim the docker.io/library if exists
  167. func trimDockerRegistry(imagename string) string {
  168. imagename = strings.Replace(imagename, "docker.io/", "", 1)
  169. return strings.Replace(imagename, "library/", "", 1)
  170. }
  171. // validatorFn is the function which is individual tests will implement.
  172. // we may want it to return more than just an error, at some point.
  173. type validatorFn func(c clientset.Interface, podID string) error
  174. // ValidateController is a generic mechanism for testing RC's that are running.
  175. // It takes a container name, a test name, and a validator function which is plugged in by a specific test.
  176. // "containername": this is grepped for.
  177. // "containerImage" : this is the name of the image we expect to be launched. Not to confuse w/ images (kitten.jpg) which are validated.
  178. // "testname": which gets bubbled up to the logging/failure messages if errors happen.
  179. // "validator" function: This function is given a podID and a client, and it can do some specific validations that way.
  180. func ValidateController(c clientset.Interface, containerImage string, replicas int, containername string, testname string, validator validatorFn, ns string) {
  181. containerImage = trimDockerRegistry(containerImage)
  182. getPodsTemplate := "--template={{range.items}}{{.metadata.name}} {{end}}"
  183. // NB: kubectl adds the "exists" function to the standard template functions.
  184. // This lets us check to see if the "running" entry exists for each of the containers
  185. // we care about. Exists will never return an error and it's safe to check a chain of
  186. // things, any one of which may not exist. In the below template, all of info,
  187. // containername, and running might be nil, so the normal index function isn't very
  188. // helpful.
  189. // This template is unit-tested in kubectl, so if you change it, update the unit test.
  190. // You can read about the syntax here: http://golang.org/pkg/text/template/.
  191. getContainerStateTemplate := fmt.Sprintf(`--template={{if (exists . "status" "containerStatuses")}}{{range .status.containerStatuses}}{{if (and (eq .name "%s") (exists . "state" "running"))}}true{{end}}{{end}}{{end}}`, containername)
  192. getImageTemplate := fmt.Sprintf(`--template={{if (exists . "spec" "containers")}}{{range .spec.containers}}{{if eq .name "%s"}}{{.image}}{{end}}{{end}}{{end}}`, containername)
  193. ginkgo.By(fmt.Sprintf("waiting for all containers in %s pods to come up.", testname)) //testname should be selector
  194. waitLoop:
  195. for start := time.Now(); time.Since(start) < PodStartTimeout; time.Sleep(5 * time.Second) {
  196. getPodsOutput := RunKubectlOrDie("get", "pods", "-o", "template", getPodsTemplate, "-l", testname, fmt.Sprintf("--namespace=%v", ns))
  197. pods := strings.Fields(getPodsOutput)
  198. if numPods := len(pods); numPods != replicas {
  199. ginkgo.By(fmt.Sprintf("Replicas for %s: expected=%d actual=%d", testname, replicas, numPods))
  200. continue
  201. }
  202. var runningPods []string
  203. for _, podID := range pods {
  204. running := RunKubectlOrDie("get", "pods", podID, "-o", "template", getContainerStateTemplate, fmt.Sprintf("--namespace=%v", ns))
  205. if running != "true" {
  206. e2elog.Logf("%s is created but not running", podID)
  207. continue waitLoop
  208. }
  209. currentImage := RunKubectlOrDie("get", "pods", podID, "-o", "template", getImageTemplate, fmt.Sprintf("--namespace=%v", ns))
  210. currentImage = trimDockerRegistry(currentImage)
  211. if currentImage != containerImage {
  212. e2elog.Logf("%s is created but running wrong image; expected: %s, actual: %s", podID, containerImage, currentImage)
  213. continue waitLoop
  214. }
  215. // Call the generic validator function here.
  216. // This might validate for example, that (1) getting a url works and (2) url is serving correct content.
  217. if err := validator(c, podID); err != nil {
  218. e2elog.Logf("%s is running right image but validator function failed: %v", podID, err)
  219. continue waitLoop
  220. }
  221. e2elog.Logf("%s is verified up and running", podID)
  222. runningPods = append(runningPods, podID)
  223. }
  224. // If we reach here, then all our checks passed.
  225. if len(runningPods) == replicas {
  226. return
  227. }
  228. }
  229. // Reaching here means that one of more checks failed multiple times. Assuming its not a race condition, something is broken.
  230. Failf("Timed out after %v seconds waiting for %s pods to reach valid state", PodStartTimeout.Seconds(), testname)
  231. }