testserver.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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 testing
  14. import (
  15. "context"
  16. "fmt"
  17. "io/ioutil"
  18. "net"
  19. "os"
  20. "path"
  21. "runtime"
  22. "time"
  23. "github.com/spf13/pflag"
  24. "k8s.io/apimachinery/pkg/api/errors"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/util/wait"
  27. "k8s.io/apiserver/pkg/registry/generic/registry"
  28. "k8s.io/apiserver/pkg/storage/storagebackend"
  29. "k8s.io/client-go/kubernetes"
  30. restclient "k8s.io/client-go/rest"
  31. "k8s.io/client-go/util/cert"
  32. "k8s.io/kubernetes/cmd/kube-apiserver/app"
  33. "k8s.io/kubernetes/cmd/kube-apiserver/app/options"
  34. testutil "k8s.io/kubernetes/test/utils"
  35. )
  36. // TearDownFunc is to be called to tear down a test server.
  37. type TearDownFunc func()
  38. // TestServerInstanceOptions Instance options the TestServer
  39. type TestServerInstanceOptions struct {
  40. // DisableStorageCleanup Disable the automatic storage cleanup
  41. DisableStorageCleanup bool
  42. // Enable cert-auth for the kube-apiserver
  43. EnableCertAuth bool
  44. }
  45. // TestServer return values supplied by kube-test-ApiServer
  46. type TestServer struct {
  47. ClientConfig *restclient.Config // Rest client config
  48. ServerOpts *options.ServerRunOptions // ServerOpts
  49. TearDownFn TearDownFunc // TearDown function
  50. TmpDir string // Temp Dir used, by the apiserver
  51. }
  52. // Logger allows t.Testing and b.Testing to be passed to StartTestServer and StartTestServerOrDie
  53. type Logger interface {
  54. Errorf(format string, args ...interface{})
  55. Fatalf(format string, args ...interface{})
  56. Logf(format string, args ...interface{})
  57. }
  58. // NewDefaultTestServerOptions Default options for TestServer instances
  59. func NewDefaultTestServerOptions() *TestServerInstanceOptions {
  60. return &TestServerInstanceOptions{
  61. DisableStorageCleanup: false,
  62. EnableCertAuth: true,
  63. }
  64. }
  65. // StartTestServer starts a etcd server and kube-apiserver. A rest client config and a tear-down func,
  66. // and location of the tmpdir are returned.
  67. //
  68. // Note: we return a tear-down func instead of a stop channel because the later will leak temporary
  69. // files that because Golang testing's call to os.Exit will not give a stop channel go routine
  70. // enough time to remove temporary files.
  71. func StartTestServer(t Logger, instanceOptions *TestServerInstanceOptions, customFlags []string, storageConfig *storagebackend.Config) (result TestServer, err error) {
  72. if instanceOptions == nil {
  73. instanceOptions = NewDefaultTestServerOptions()
  74. }
  75. // TODO : Remove TrackStorageCleanup below when PR
  76. // https://github.com/kubernetes/kubernetes/pull/50690
  77. // merges as that shuts down storage properly
  78. if !instanceOptions.DisableStorageCleanup {
  79. registry.TrackStorageCleanup()
  80. }
  81. stopCh := make(chan struct{})
  82. tearDown := func() {
  83. if !instanceOptions.DisableStorageCleanup {
  84. registry.CleanupStorage()
  85. }
  86. close(stopCh)
  87. if len(result.TmpDir) != 0 {
  88. os.RemoveAll(result.TmpDir)
  89. }
  90. }
  91. defer func() {
  92. if result.TearDownFn == nil {
  93. tearDown()
  94. }
  95. }()
  96. result.TmpDir, err = ioutil.TempDir("", "kubernetes-kube-apiserver")
  97. if err != nil {
  98. return result, fmt.Errorf("failed to create temp dir: %v", err)
  99. }
  100. fs := pflag.NewFlagSet("test", pflag.PanicOnError)
  101. s := options.NewServerRunOptions()
  102. for _, f := range s.Flags().FlagSets {
  103. fs.AddFlagSet(f)
  104. }
  105. s.InsecureServing.BindPort = 0
  106. s.SecureServing.Listener, s.SecureServing.BindPort, err = createLocalhostListenerOnFreePort()
  107. if err != nil {
  108. return result, fmt.Errorf("failed to create listener: %v", err)
  109. }
  110. s.SecureServing.ServerCert.CertDirectory = result.TmpDir
  111. if instanceOptions.EnableCertAuth {
  112. // create certificates for aggregation and client-cert auth
  113. proxySigningKey, err := testutil.NewPrivateKey()
  114. if err != nil {
  115. return result, err
  116. }
  117. proxySigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "front-proxy-ca"}, proxySigningKey)
  118. if err != nil {
  119. return result, err
  120. }
  121. proxyCACertFile := path.Join(s.SecureServing.ServerCert.CertDirectory, "proxy-ca.crt")
  122. if err := ioutil.WriteFile(proxyCACertFile, testutil.EncodeCertPEM(proxySigningCert), 0644); err != nil {
  123. return result, err
  124. }
  125. s.Authentication.RequestHeader.ClientCAFile = proxyCACertFile
  126. clientSigningKey, err := testutil.NewPrivateKey()
  127. if err != nil {
  128. return result, err
  129. }
  130. clientSigningCert, err := cert.NewSelfSignedCACert(cert.Config{CommonName: "client-ca"}, clientSigningKey)
  131. if err != nil {
  132. return result, err
  133. }
  134. clientCACertFile := path.Join(s.SecureServing.ServerCert.CertDirectory, "client-ca.crt")
  135. if err := ioutil.WriteFile(clientCACertFile, testutil.EncodeCertPEM(clientSigningCert), 0644); err != nil {
  136. return result, err
  137. }
  138. s.Authentication.ClientCert.ClientCA = clientCACertFile
  139. }
  140. s.SecureServing.ExternalAddress = s.SecureServing.Listener.Addr().(*net.TCPAddr).IP // use listener addr although it is a loopback device
  141. _, thisFile, _, ok := runtime.Caller(0)
  142. if !ok {
  143. return result, fmt.Errorf("failed to get current file")
  144. }
  145. s.SecureServing.ServerCert.FixtureDirectory = path.Join(path.Dir(thisFile), "testdata")
  146. s.ServiceClusterIPRanges = "10.0.0.0/16"
  147. s.Etcd.StorageConfig = *storageConfig
  148. s.APIEnablement.RuntimeConfig.Set("api/all=true")
  149. if err := fs.Parse(customFlags); err != nil {
  150. return result, err
  151. }
  152. completedOptions, err := app.Complete(s)
  153. if err != nil {
  154. return result, fmt.Errorf("failed to set default ServerRunOptions: %v", err)
  155. }
  156. t.Logf("runtime-config=%v", completedOptions.APIEnablement.RuntimeConfig)
  157. t.Logf("Starting kube-apiserver on port %d...", s.SecureServing.BindPort)
  158. server, err := app.CreateServerChain(completedOptions, stopCh)
  159. if err != nil {
  160. return result, fmt.Errorf("failed to create server chain: %v", err)
  161. }
  162. errCh := make(chan error)
  163. go func(stopCh <-chan struct{}) {
  164. prepared, err := server.PrepareRun()
  165. if err != nil {
  166. errCh <- err
  167. } else if err := prepared.Run(stopCh); err != nil {
  168. errCh <- err
  169. }
  170. }(stopCh)
  171. t.Logf("Waiting for /healthz to be ok...")
  172. client, err := kubernetes.NewForConfig(server.GenericAPIServer.LoopbackClientConfig)
  173. if err != nil {
  174. return result, fmt.Errorf("failed to create a client: %v", err)
  175. }
  176. // wait until healthz endpoint returns ok
  177. err = wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) {
  178. select {
  179. case err := <-errCh:
  180. return false, err
  181. default:
  182. }
  183. result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(context.TODO())
  184. status := 0
  185. result.StatusCode(&status)
  186. if status == 200 {
  187. return true, nil
  188. }
  189. return false, nil
  190. })
  191. if err != nil {
  192. return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err)
  193. }
  194. // wait until default namespace is created
  195. err = wait.Poll(100*time.Millisecond, 30*time.Second, func() (bool, error) {
  196. select {
  197. case err := <-errCh:
  198. return false, err
  199. default:
  200. }
  201. if _, err := client.CoreV1().Namespaces().Get(context.TODO(), "default", metav1.GetOptions{}); err != nil {
  202. if !errors.IsNotFound(err) {
  203. t.Logf("Unable to get default namespace: %v", err)
  204. }
  205. return false, nil
  206. }
  207. return true, nil
  208. })
  209. if err != nil {
  210. return result, fmt.Errorf("failed to wait for default namespace to be created: %v", err)
  211. }
  212. // from here the caller must call tearDown
  213. result.ClientConfig = restclient.CopyConfig(server.GenericAPIServer.LoopbackClientConfig)
  214. result.ClientConfig.QPS = 1000
  215. result.ClientConfig.Burst = 10000
  216. result.ServerOpts = s
  217. result.TearDownFn = tearDown
  218. return result, nil
  219. }
  220. // StartTestServerOrDie calls StartTestServer t.Fatal if it does not succeed.
  221. func StartTestServerOrDie(t Logger, instanceOptions *TestServerInstanceOptions, flags []string, storageConfig *storagebackend.Config) *TestServer {
  222. result, err := StartTestServer(t, instanceOptions, flags, storageConfig)
  223. if err == nil {
  224. return &result
  225. }
  226. t.Fatalf("failed to launch server: %v", err)
  227. return nil
  228. }
  229. func createLocalhostListenerOnFreePort() (net.Listener, int, error) {
  230. ln, err := net.Listen("tcp", "127.0.0.1:0")
  231. if err != nil {
  232. return nil, 0, err
  233. }
  234. // get port
  235. tcpAddr, ok := ln.Addr().(*net.TCPAddr)
  236. if !ok {
  237. ln.Close()
  238. return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
  239. }
  240. return ln, tcpAddr.Port, nil
  241. }