1
16
17 package testing
18
19 import (
20 "context"
21 "fmt"
22 "net"
23 "os"
24 "time"
25
26 "github.com/spf13/pflag"
27
28 "k8s.io/apimachinery/pkg/util/wait"
29 "k8s.io/client-go/kubernetes"
30 restclient "k8s.io/client-go/rest"
31 "k8s.io/component-base/configz"
32 logsapi "k8s.io/component-base/logs/api/v1"
33 "k8s.io/kubernetes/cmd/kube-scheduler/app"
34 kubeschedulerconfig "k8s.io/kubernetes/cmd/kube-scheduler/app/config"
35 "k8s.io/kubernetes/cmd/kube-scheduler/app/options"
36
37 "k8s.io/klog/v2"
38 )
39
40 func init() {
41
42
43
44 logsapi.ReapplyHandling = logsapi.ReapplyHandlingIgnoreUnchanged
45 }
46
47
48 type TearDownFunc func()
49
50
51 type TestServer struct {
52 LoopbackClientConfig *restclient.Config
53 Options *options.Options
54 Config *kubeschedulerconfig.Config
55 TearDownFn TearDownFunc
56 TmpDir string
57 }
58
59
60
61
62
63
64
65
66 func StartTestServer(ctx context.Context, customFlags []string) (result TestServer, err error) {
67 logger := klog.FromContext(ctx)
68 ctx, cancel := context.WithCancel(ctx)
69
70 var errCh chan error
71 tearDown := func() {
72 cancel()
73
74
75
76 if errCh != nil {
77 err, ok := <-errCh
78 if ok && err != nil {
79 logger.Error(err, "Failed to shutdown test server clearly")
80 }
81 }
82 if len(result.TmpDir) != 0 {
83 os.RemoveAll(result.TmpDir)
84 }
85 configz.Delete("componentconfig")
86 }
87 defer func() {
88 if result.TearDownFn == nil {
89 tearDown()
90 }
91 }()
92
93 result.TmpDir, err = os.MkdirTemp("", "kube-scheduler")
94 if err != nil {
95 return result, fmt.Errorf("failed to create temp dir: %v", err)
96 }
97
98 fs := pflag.NewFlagSet("test", pflag.PanicOnError)
99
100 opts := options.NewOptions()
101 nfs := opts.Flags
102 for _, f := range nfs.FlagSets {
103 fs.AddFlagSet(f)
104 }
105 fs.Parse(customFlags)
106
107 if opts.SecureServing.BindPort != 0 {
108 opts.SecureServing.Listener, opts.SecureServing.BindPort, err = createListenerOnFreePort()
109 if err != nil {
110 return result, fmt.Errorf("failed to create listener: %v", err)
111 }
112 opts.SecureServing.ServerCert.CertDirectory = result.TmpDir
113
114 logger.Info("kube-scheduler will listen securely", "port", opts.SecureServing.BindPort)
115 }
116
117 cc, sched, err := app.Setup(ctx, opts)
118 if err != nil {
119 return result, fmt.Errorf("failed to create config from options: %v", err)
120 }
121
122 errCh = make(chan error)
123 go func(ctx context.Context) {
124 defer close(errCh)
125 if err := app.Run(ctx, cc, sched); err != nil {
126 errCh <- err
127 }
128 }(ctx)
129
130 logger.Info("Waiting for /healthz to be ok...")
131 client, err := kubernetes.NewForConfig(cc.LoopbackClientConfig)
132 if err != nil {
133 return result, fmt.Errorf("failed to create a client: %v", err)
134 }
135 err = wait.PollUntilContextTimeout(ctx, 100*time.Millisecond, 30*time.Second, false, func(ctx context.Context) (bool, error) {
136 select {
137 case err := <-errCh:
138 return false, err
139 default:
140 }
141
142 result := client.CoreV1().RESTClient().Get().AbsPath("/healthz").Do(ctx)
143 status := 0
144 result.StatusCode(&status)
145 if status == 200 {
146 return true, nil
147 }
148 return false, nil
149 })
150 if err != nil {
151 return result, fmt.Errorf("failed to wait for /healthz to return ok: %v", err)
152 }
153
154
155 result.LoopbackClientConfig = cc.LoopbackClientConfig
156 result.Options = opts
157 result.Config = cc.Config
158 result.TearDownFn = tearDown
159
160 return result, nil
161 }
162
163
164 func StartTestServerOrDie(ctx context.Context, flags []string) *TestServer {
165 result, err := StartTestServer(ctx, flags)
166 if err == nil {
167 return &result
168 }
169
170 panic(fmt.Errorf("failed to launch server: %v", err))
171 }
172
173 func createListenerOnFreePort() (net.Listener, int, error) {
174 ln, err := net.Listen("tcp", ":0")
175 if err != nil {
176 return nil, 0, err
177 }
178
179
180 tcpAddr, ok := ln.Addr().(*net.TCPAddr)
181 if !ok {
182 ln.Close()
183 return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
184 }
185
186 return ln, tcpAddr.Port, nil
187 }
188
View as plain text