options_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. /*
  2. Copyright 2018 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 options
  14. import (
  15. "fmt"
  16. "io/ioutil"
  17. "net/http"
  18. "net/http/httptest"
  19. "os"
  20. "path/filepath"
  21. "reflect"
  22. "strings"
  23. "testing"
  24. "time"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/runtime"
  27. "k8s.io/apimachinery/pkg/util/diff"
  28. apiserveroptions "k8s.io/apiserver/pkg/server/options"
  29. componentbaseconfig "k8s.io/component-base/config"
  30. kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config"
  31. )
  32. func TestSchedulerOptions(t *testing.T) {
  33. // temp dir
  34. tmpDir, err := ioutil.TempDir("", "scheduler-options")
  35. if err != nil {
  36. t.Fatal(err)
  37. }
  38. defer os.RemoveAll(tmpDir)
  39. // record the username requests were made with
  40. username := ""
  41. // https server
  42. server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  43. username, _, _ = req.BasicAuth()
  44. if username == "" {
  45. username = "none, tls"
  46. }
  47. w.WriteHeader(200)
  48. w.Write([]byte(`ok`))
  49. }))
  50. defer server.Close()
  51. // http server
  52. insecureserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  53. username, _, _ = req.BasicAuth()
  54. if username == "" {
  55. username = "none, http"
  56. }
  57. w.WriteHeader(200)
  58. w.Write([]byte(`ok`))
  59. }))
  60. defer insecureserver.Close()
  61. // config file and kubeconfig
  62. configFile := filepath.Join(tmpDir, "scheduler.yaml")
  63. configKubeconfig := filepath.Join(tmpDir, "config.kubeconfig")
  64. if err := ioutil.WriteFile(configFile, []byte(fmt.Sprintf(`
  65. apiVersion: kubescheduler.config.k8s.io/v1alpha1
  66. kind: KubeSchedulerConfiguration
  67. clientConnection:
  68. kubeconfig: "%s"
  69. leaderElection:
  70. leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil {
  71. t.Fatal(err)
  72. }
  73. if err := ioutil.WriteFile(configKubeconfig, []byte(fmt.Sprintf(`
  74. apiVersion: v1
  75. kind: Config
  76. clusters:
  77. - cluster:
  78. insecure-skip-tls-verify: true
  79. server: %s
  80. name: default
  81. contexts:
  82. - context:
  83. cluster: default
  84. user: default
  85. name: default
  86. current-context: default
  87. users:
  88. - name: default
  89. user:
  90. username: config
  91. `, server.URL)), os.FileMode(0600)); err != nil {
  92. t.Fatal(err)
  93. }
  94. oldconfigFile := filepath.Join(tmpDir, "scheduler_old.yaml")
  95. if err := ioutil.WriteFile(oldconfigFile, []byte(fmt.Sprintf(`
  96. apiVersion: componentconfig/v1alpha1
  97. kind: KubeSchedulerConfiguration
  98. clientConnection:
  99. kubeconfig: "%s"
  100. leaderElection:
  101. leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil {
  102. t.Fatal(err)
  103. }
  104. invalidconfigFile := filepath.Join(tmpDir, "scheduler_invalid.yaml")
  105. if err := ioutil.WriteFile(invalidconfigFile, []byte(fmt.Sprintf(`
  106. apiVersion: componentconfig/v1alpha2
  107. kind: KubeSchedulerConfiguration
  108. clientConnection:
  109. kubeconfig: "%s"
  110. leaderElection:
  111. leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil {
  112. t.Fatal(err)
  113. }
  114. // flag-specified kubeconfig
  115. flagKubeconfig := filepath.Join(tmpDir, "flag.kubeconfig")
  116. if err := ioutil.WriteFile(flagKubeconfig, []byte(fmt.Sprintf(`
  117. apiVersion: v1
  118. kind: Config
  119. clusters:
  120. - cluster:
  121. insecure-skip-tls-verify: true
  122. server: %s
  123. name: default
  124. contexts:
  125. - context:
  126. cluster: default
  127. user: default
  128. name: default
  129. current-context: default
  130. users:
  131. - name: default
  132. user:
  133. username: flag
  134. `, server.URL)), os.FileMode(0600)); err != nil {
  135. t.Fatal(err)
  136. }
  137. // plugin config
  138. pluginconfigFile := filepath.Join(tmpDir, "plugin.yaml")
  139. if err := ioutil.WriteFile(pluginconfigFile, []byte(fmt.Sprintf(`
  140. apiVersion: kubescheduler.config.k8s.io/v1alpha1
  141. kind: KubeSchedulerConfiguration
  142. clientConnection:
  143. kubeconfig: "%s"
  144. plugins:
  145. reserve:
  146. enabled:
  147. - name: foo
  148. - name: bar
  149. disabled:
  150. - name: baz
  151. preBind:
  152. enabled:
  153. - name: foo
  154. disabled:
  155. - name: baz
  156. pluginConfig:
  157. - name: foo
  158. `, configKubeconfig)), os.FileMode(0600)); err != nil {
  159. t.Fatal(err)
  160. }
  161. // Insulate this test from picking up in-cluster config when run inside a pod
  162. // We can't assume we have permissions to write to /var/run/secrets/... from a unit test to mock in-cluster config for testing
  163. originalHost := os.Getenv("KUBERNETES_SERVICE_HOST")
  164. if len(originalHost) > 0 {
  165. os.Setenv("KUBERNETES_SERVICE_HOST", "")
  166. defer os.Setenv("KUBERNETES_SERVICE_HOST", originalHost)
  167. }
  168. defaultSource := "DefaultProvider"
  169. defaultBindTimeoutSeconds := int64(600)
  170. testcases := []struct {
  171. name string
  172. options *Options
  173. expectedUsername string
  174. expectedError string
  175. expectedConfig kubeschedulerconfig.KubeSchedulerConfiguration
  176. }{
  177. {
  178. name: "config file",
  179. options: &Options{
  180. ConfigFile: configFile,
  181. ComponentConfig: func() kubeschedulerconfig.KubeSchedulerConfiguration {
  182. cfg, err := newDefaultComponentConfig()
  183. if err != nil {
  184. t.Fatal(err)
  185. }
  186. return *cfg
  187. }(),
  188. SecureServing: (&apiserveroptions.SecureServingOptions{
  189. ServerCert: apiserveroptions.GeneratableKeyCert{
  190. CertDirectory: "/a/b/c",
  191. PairName: "kube-scheduler",
  192. },
  193. HTTP2MaxStreamsPerConnection: 47,
  194. }).WithLoopback(),
  195. Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
  196. CacheTTL: 10 * time.Second,
  197. ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
  198. RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
  199. UsernameHeaders: []string{"x-remote-user"},
  200. GroupHeaders: []string{"x-remote-group"},
  201. ExtraHeaderPrefixes: []string{"x-remote-extra-"},
  202. },
  203. RemoteKubeConfigFileOptional: true,
  204. },
  205. Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
  206. AllowCacheTTL: 10 * time.Second,
  207. DenyCacheTTL: 10 * time.Second,
  208. RemoteKubeConfigFileOptional: true,
  209. AlwaysAllowPaths: []string{"/healthz"}, // note: this does not match /healthz/ or /healthz/*
  210. },
  211. },
  212. expectedUsername: "config",
  213. expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{
  214. SchedulerName: "default-scheduler",
  215. AlgorithmSource: kubeschedulerconfig.SchedulerAlgorithmSource{Provider: &defaultSource},
  216. HardPodAffinitySymmetricWeight: 1,
  217. HealthzBindAddress: "0.0.0.0:10251",
  218. MetricsBindAddress: "0.0.0.0:10251",
  219. LeaderElection: kubeschedulerconfig.KubeSchedulerLeaderElectionConfiguration{
  220. LeaderElectionConfiguration: componentbaseconfig.LeaderElectionConfiguration{
  221. LeaderElect: true,
  222. LeaseDuration: metav1.Duration{Duration: 15 * time.Second},
  223. RenewDeadline: metav1.Duration{Duration: 10 * time.Second},
  224. RetryPeriod: metav1.Duration{Duration: 2 * time.Second},
  225. ResourceLock: "endpoints",
  226. },
  227. LockObjectNamespace: "kube-system",
  228. LockObjectName: "kube-scheduler",
  229. },
  230. ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
  231. Kubeconfig: configKubeconfig,
  232. QPS: 50,
  233. Burst: 100,
  234. ContentType: "application/vnd.kubernetes.protobuf",
  235. },
  236. BindTimeoutSeconds: &defaultBindTimeoutSeconds,
  237. Plugins: nil,
  238. },
  239. },
  240. {
  241. name: "config file in componentconfig/v1alpha1",
  242. options: &Options{
  243. ConfigFile: oldconfigFile,
  244. ComponentConfig: func() kubeschedulerconfig.KubeSchedulerConfiguration {
  245. cfg, err := newDefaultComponentConfig()
  246. if err != nil {
  247. t.Fatal(err)
  248. }
  249. return *cfg
  250. }(),
  251. },
  252. expectedError: "no kind \"KubeSchedulerConfiguration\" is registered for version \"componentconfig/v1alpha1\"",
  253. },
  254. {
  255. name: "invalid config file in componentconfig/v1alpha2",
  256. options: &Options{ConfigFile: invalidconfigFile},
  257. expectedError: "no kind \"KubeSchedulerConfiguration\" is registered for version \"componentconfig/v1alpha2\"",
  258. },
  259. {
  260. name: "kubeconfig flag",
  261. options: &Options{
  262. ComponentConfig: func() kubeschedulerconfig.KubeSchedulerConfiguration {
  263. cfg, _ := newDefaultComponentConfig()
  264. cfg.ClientConnection.Kubeconfig = flagKubeconfig
  265. return *cfg
  266. }(),
  267. SecureServing: (&apiserveroptions.SecureServingOptions{
  268. ServerCert: apiserveroptions.GeneratableKeyCert{
  269. CertDirectory: "/a/b/c",
  270. PairName: "kube-scheduler",
  271. },
  272. HTTP2MaxStreamsPerConnection: 47,
  273. }).WithLoopback(),
  274. Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
  275. CacheTTL: 10 * time.Second,
  276. ClientCert: apiserveroptions.ClientCertAuthenticationOptions{},
  277. RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
  278. UsernameHeaders: []string{"x-remote-user"},
  279. GroupHeaders: []string{"x-remote-group"},
  280. ExtraHeaderPrefixes: []string{"x-remote-extra-"},
  281. },
  282. RemoteKubeConfigFileOptional: true,
  283. },
  284. Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
  285. AllowCacheTTL: 10 * time.Second,
  286. DenyCacheTTL: 10 * time.Second,
  287. RemoteKubeConfigFileOptional: true,
  288. AlwaysAllowPaths: []string{"/healthz"}, // note: this does not match /healthz/ or /healthz/*
  289. },
  290. },
  291. expectedUsername: "flag",
  292. expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{
  293. SchedulerName: "default-scheduler",
  294. AlgorithmSource: kubeschedulerconfig.SchedulerAlgorithmSource{Provider: &defaultSource},
  295. HardPodAffinitySymmetricWeight: 1,
  296. HealthzBindAddress: "", // defaults empty when not running from config file
  297. MetricsBindAddress: "", // defaults empty when not running from config file
  298. LeaderElection: kubeschedulerconfig.KubeSchedulerLeaderElectionConfiguration{
  299. LeaderElectionConfiguration: componentbaseconfig.LeaderElectionConfiguration{
  300. LeaderElect: true,
  301. LeaseDuration: metav1.Duration{Duration: 15 * time.Second},
  302. RenewDeadline: metav1.Duration{Duration: 10 * time.Second},
  303. RetryPeriod: metav1.Duration{Duration: 2 * time.Second},
  304. ResourceLock: "endpoints",
  305. },
  306. LockObjectNamespace: "kube-system",
  307. LockObjectName: "kube-scheduler",
  308. },
  309. ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
  310. Kubeconfig: flagKubeconfig,
  311. QPS: 50,
  312. Burst: 100,
  313. ContentType: "application/vnd.kubernetes.protobuf",
  314. },
  315. BindTimeoutSeconds: &defaultBindTimeoutSeconds,
  316. },
  317. },
  318. {
  319. name: "overridden master",
  320. options: &Options{
  321. Master: insecureserver.URL,
  322. SecureServing: (&apiserveroptions.SecureServingOptions{
  323. ServerCert: apiserveroptions.GeneratableKeyCert{
  324. CertDirectory: "/a/b/c",
  325. PairName: "kube-scheduler",
  326. },
  327. HTTP2MaxStreamsPerConnection: 47,
  328. }).WithLoopback(),
  329. Authentication: &apiserveroptions.DelegatingAuthenticationOptions{
  330. CacheTTL: 10 * time.Second,
  331. RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{
  332. UsernameHeaders: []string{"x-remote-user"},
  333. GroupHeaders: []string{"x-remote-group"},
  334. ExtraHeaderPrefixes: []string{"x-remote-extra-"},
  335. },
  336. RemoteKubeConfigFileOptional: true,
  337. },
  338. Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
  339. AllowCacheTTL: 10 * time.Second,
  340. DenyCacheTTL: 10 * time.Second,
  341. RemoteKubeConfigFileOptional: true,
  342. AlwaysAllowPaths: []string{"/healthz"}, // note: this does not match /healthz/ or /healthz/*
  343. },
  344. },
  345. expectedUsername: "none, http",
  346. },
  347. {
  348. name: "plugin config",
  349. options: &Options{
  350. ConfigFile: pluginconfigFile,
  351. },
  352. expectedUsername: "config",
  353. expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{
  354. SchedulerName: "default-scheduler",
  355. AlgorithmSource: kubeschedulerconfig.SchedulerAlgorithmSource{Provider: &defaultSource},
  356. HardPodAffinitySymmetricWeight: 1,
  357. HealthzBindAddress: "0.0.0.0:10251",
  358. MetricsBindAddress: "0.0.0.0:10251",
  359. LeaderElection: kubeschedulerconfig.KubeSchedulerLeaderElectionConfiguration{
  360. LeaderElectionConfiguration: componentbaseconfig.LeaderElectionConfiguration{
  361. LeaderElect: true,
  362. LeaseDuration: metav1.Duration{Duration: 15 * time.Second},
  363. RenewDeadline: metav1.Duration{Duration: 10 * time.Second},
  364. RetryPeriod: metav1.Duration{Duration: 2 * time.Second},
  365. ResourceLock: "endpoints",
  366. },
  367. LockObjectNamespace: "kube-system",
  368. LockObjectName: "kube-scheduler",
  369. },
  370. ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
  371. Kubeconfig: configKubeconfig,
  372. QPS: 50,
  373. Burst: 100,
  374. ContentType: "application/vnd.kubernetes.protobuf",
  375. },
  376. BindTimeoutSeconds: &defaultBindTimeoutSeconds,
  377. Plugins: &kubeschedulerconfig.Plugins{
  378. Reserve: &kubeschedulerconfig.PluginSet{
  379. Enabled: []kubeschedulerconfig.Plugin{
  380. {
  381. Name: "foo",
  382. },
  383. {
  384. Name: "bar",
  385. },
  386. },
  387. Disabled: []kubeschedulerconfig.Plugin{
  388. {
  389. Name: "baz",
  390. },
  391. },
  392. },
  393. PreBind: &kubeschedulerconfig.PluginSet{
  394. Enabled: []kubeschedulerconfig.Plugin{
  395. {
  396. Name: "foo",
  397. },
  398. },
  399. Disabled: []kubeschedulerconfig.Plugin{
  400. {
  401. Name: "baz",
  402. },
  403. },
  404. },
  405. },
  406. PluginConfig: []kubeschedulerconfig.PluginConfig{
  407. {
  408. Name: "foo",
  409. Args: runtime.Unknown{},
  410. },
  411. },
  412. },
  413. },
  414. {
  415. name: "no config",
  416. options: &Options{},
  417. expectedError: "no configuration has been provided",
  418. },
  419. }
  420. for _, tc := range testcases {
  421. t.Run(tc.name, func(t *testing.T) {
  422. // create the config
  423. config, err := tc.options.Config()
  424. // handle errors
  425. if err != nil {
  426. if tc.expectedError == "" {
  427. t.Error(err)
  428. } else if !strings.Contains(err.Error(), tc.expectedError) {
  429. t.Errorf("expected %q, got %q", tc.expectedError, err.Error())
  430. }
  431. return
  432. }
  433. if !reflect.DeepEqual(config.ComponentConfig, tc.expectedConfig) {
  434. t.Errorf("config.diff:\n%s", diff.ObjectReflectDiff(tc.expectedConfig, config.ComponentConfig))
  435. }
  436. // ensure we have a client
  437. if config.Client == nil {
  438. t.Error("unexpected nil client")
  439. return
  440. }
  441. // test the client talks to the endpoint we expect with the credentials we expect
  442. username = ""
  443. _, err = config.Client.Discovery().RESTClient().Get().AbsPath("/").DoRaw()
  444. if err != nil {
  445. t.Error(err)
  446. return
  447. }
  448. if username != tc.expectedUsername {
  449. t.Errorf("expected server call with user %s, got %s", tc.expectedUsername, username)
  450. }
  451. })
  452. }
  453. }