services.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /*
  2. Copyright 2016 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 services
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "os"
  18. "os/exec"
  19. "path"
  20. "testing"
  21. "github.com/kardianos/osext"
  22. "k8s.io/klog"
  23. utilfeature "k8s.io/apiserver/pkg/util/feature"
  24. "k8s.io/kubernetes/test/e2e/framework"
  25. )
  26. // E2EServices starts and stops e2e services in a separate process. The test
  27. // uses it to start and stop all e2e services.
  28. type E2EServices struct {
  29. // monitorParent determines whether the sub-processes should watch and die with the current
  30. // process.
  31. rmDirs []string
  32. monitorParent bool
  33. services *server
  34. kubelet *server
  35. logs logFiles
  36. }
  37. // NewE2EServices returns a new E2EServices instance.
  38. func NewE2EServices(monitorParent bool) *E2EServices {
  39. return &E2EServices{
  40. monitorParent: monitorParent,
  41. // Special log files that need to be collected for additional debugging.
  42. logs: getLogFiles(),
  43. }
  44. }
  45. // Start starts the e2e services in another process by calling back into the
  46. // test binary. Returns when all e2e services are ready or an error.
  47. //
  48. // We want to statically link e2e services into the test binary, but we don't
  49. // want their glog output to pollute the test result. So we run the binary in
  50. // run-services-mode to start e2e services in another process.
  51. // The function starts 2 processes:
  52. // * internal e2e services: services which statically linked in the test binary - apiserver, etcd and
  53. // namespace controller.
  54. // * kubelet: kubelet binary is outside. (We plan to move main kubelet start logic out when we have
  55. // standard kubelet launcher)
  56. func (e *E2EServices) Start() error {
  57. var err error
  58. if !framework.TestContext.NodeConformance {
  59. // Start kubelet
  60. e.kubelet, err = e.startKubelet()
  61. if err != nil {
  62. return fmt.Errorf("failed to start kubelet: %v", err)
  63. }
  64. }
  65. e.services, err = e.startInternalServices()
  66. return err
  67. }
  68. // Stop stops the e2e services.
  69. func (e *E2EServices) Stop() {
  70. defer func() {
  71. if !framework.TestContext.NodeConformance {
  72. // Collect log files.
  73. e.collectLogFiles()
  74. }
  75. }()
  76. if e.services != nil {
  77. if err := e.services.kill(); err != nil {
  78. klog.Errorf("Failed to stop services: %v", err)
  79. }
  80. }
  81. if e.kubelet != nil {
  82. if err := e.kubelet.kill(); err != nil {
  83. klog.Errorf("Failed to stop kubelet: %v", err)
  84. }
  85. }
  86. if e.rmDirs != nil {
  87. for _, d := range e.rmDirs {
  88. err := os.RemoveAll(d)
  89. if err != nil {
  90. klog.Errorf("Failed to delete directory %s: %v", d, err)
  91. }
  92. }
  93. }
  94. }
  95. // RunE2EServices actually start the e2e services. This function is used to
  96. // start e2e services in current process. This is only used in run-services-mode.
  97. func RunE2EServices(t *testing.T) {
  98. // Populate global DefaultFeatureGate with value from TestContext.FeatureGates.
  99. // This way, statically-linked components see the same feature gate config as the test context.
  100. if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(framework.TestContext.FeatureGates); err != nil {
  101. t.Fatal(err)
  102. }
  103. e := newE2EServices()
  104. if err := e.run(t); err != nil {
  105. klog.Fatalf("Failed to run e2e services: %v", err)
  106. }
  107. }
  108. const (
  109. // services.log is the combined log of all services
  110. servicesLogFile = "services.log"
  111. // LogVerbosityLevel is consistent with the level used in a cluster e2e test.
  112. LogVerbosityLevel = "4"
  113. )
  114. // startInternalServices starts the internal services in a separate process.
  115. func (e *E2EServices) startInternalServices() (*server, error) {
  116. testBin, err := osext.Executable()
  117. if err != nil {
  118. return nil, fmt.Errorf("can't get current binary: %v", err)
  119. }
  120. // Pass all flags into the child process, so that it will see the same flag set.
  121. startCmd := exec.Command(testBin, append([]string{"--run-services-mode"}, os.Args[1:]...)...)
  122. server := newServer("services", startCmd, nil, nil, getServicesHealthCheckURLs(), servicesLogFile, e.monitorParent, false)
  123. return server, server.start()
  124. }
  125. // collectLogFiles collects logs of interest either via journalctl or by creating sym
  126. // links. Since we scp files from the remote directory, symlinks will be
  127. // treated as normal files and file contents will be copied over.
  128. func (e *E2EServices) collectLogFiles() {
  129. // Nothing to do if report dir is not specified.
  130. if framework.TestContext.ReportDir == "" {
  131. return
  132. }
  133. klog.Info("Fetching log files...")
  134. journaldFound := isJournaldAvailable()
  135. for targetFileName, log := range e.logs {
  136. targetLink := path.Join(framework.TestContext.ReportDir, targetFileName)
  137. if journaldFound {
  138. // Skip log files that do not have an equivalent in journald-based machines.
  139. if len(log.JournalctlCommand) == 0 {
  140. continue
  141. }
  142. klog.Infof("Get log file %q with journalctl command %v.", targetFileName, log.JournalctlCommand)
  143. out, err := exec.Command("journalctl", log.JournalctlCommand...).CombinedOutput()
  144. if err != nil {
  145. klog.Errorf("failed to get %q from journald: %v, %v", targetFileName, string(out), err)
  146. } else {
  147. if err = ioutil.WriteFile(targetLink, out, 0644); err != nil {
  148. klog.Errorf("failed to write logs to %q: %v", targetLink, err)
  149. }
  150. }
  151. continue
  152. }
  153. for _, file := range log.Files {
  154. if _, err := os.Stat(file); err != nil {
  155. // Expected file not found on this distro.
  156. continue
  157. }
  158. if err := copyLogFile(file, targetLink); err != nil {
  159. klog.Error(err)
  160. } else {
  161. break
  162. }
  163. }
  164. }
  165. }
  166. // isJournaldAvailable returns whether the system executing the tests uses
  167. // journald.
  168. func isJournaldAvailable() bool {
  169. _, err := exec.LookPath("journalctl")
  170. return err == nil
  171. }
  172. func copyLogFile(src, target string) error {
  173. // If not a journald based distro, then just symlink files.
  174. if out, err := exec.Command("cp", src, target).CombinedOutput(); err != nil {
  175. return fmt.Errorf("failed to copy %q to %q: %v, %v", src, target, out, err)
  176. }
  177. if out, err := exec.Command("chmod", "a+r", target).CombinedOutput(); err != nil {
  178. return fmt.Errorf("failed to make log file %q world readable: %v, %v", target, out, err)
  179. }
  180. return nil
  181. }