etcd_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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 etcd
  14. import (
  15. "fmt"
  16. "reflect"
  17. "strconv"
  18. "testing"
  19. "github.com/pkg/errors"
  20. apierrors "k8s.io/apimachinery/pkg/api/errors"
  21. "k8s.io/apimachinery/pkg/runtime"
  22. "k8s.io/apimachinery/pkg/util/wait"
  23. clientsetfake "k8s.io/client-go/kubernetes/fake"
  24. clienttesting "k8s.io/client-go/testing"
  25. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  26. "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  27. testresources "k8s.io/kubernetes/cmd/kubeadm/test/resources"
  28. )
  29. func testGetURL(t *testing.T, getURLFunc func(*kubeadmapi.APIEndpoint) string, port int) {
  30. portStr := strconv.Itoa(port)
  31. var tests = []struct {
  32. name string
  33. advertiseAddress string
  34. expectedURL string
  35. }{
  36. {
  37. name: "IPv4",
  38. advertiseAddress: "10.10.10.10",
  39. expectedURL: fmt.Sprintf("https://10.10.10.10:%s", portStr),
  40. },
  41. {
  42. name: "IPv6",
  43. advertiseAddress: "2001:db8::2",
  44. expectedURL: fmt.Sprintf("https://[2001:db8::2]:%s", portStr),
  45. },
  46. {
  47. name: "IPv4 localhost",
  48. advertiseAddress: "127.0.0.1",
  49. expectedURL: fmt.Sprintf("https://127.0.0.1:%s", portStr),
  50. },
  51. {
  52. name: "IPv6 localhost",
  53. advertiseAddress: "::1",
  54. expectedURL: fmt.Sprintf("https://[::1]:%s", portStr),
  55. },
  56. }
  57. for _, test := range tests {
  58. url := getURLFunc(&kubeadmapi.APIEndpoint{AdvertiseAddress: test.advertiseAddress})
  59. if url != test.expectedURL {
  60. t.Errorf("expected %s, got %s", test.expectedURL, url)
  61. }
  62. }
  63. }
  64. func TestGetClientURL(t *testing.T) {
  65. testGetURL(t, GetClientURL, constants.EtcdListenClientPort)
  66. }
  67. func TestGetPeerURL(t *testing.T) {
  68. testGetURL(t, GetClientURL, constants.EtcdListenClientPort)
  69. }
  70. func TestGetClientURLByIP(t *testing.T) {
  71. portStr := strconv.Itoa(constants.EtcdListenClientPort)
  72. var tests = []struct {
  73. name string
  74. ip string
  75. expectedURL string
  76. }{
  77. {
  78. name: "IPv4",
  79. ip: "10.10.10.10",
  80. expectedURL: fmt.Sprintf("https://10.10.10.10:%s", portStr),
  81. },
  82. {
  83. name: "IPv6",
  84. ip: "2001:db8::2",
  85. expectedURL: fmt.Sprintf("https://[2001:db8::2]:%s", portStr),
  86. },
  87. {
  88. name: "IPv4 localhost",
  89. ip: "127.0.0.1",
  90. expectedURL: fmt.Sprintf("https://127.0.0.1:%s", portStr),
  91. },
  92. {
  93. name: "IPv6 localhost",
  94. ip: "::1",
  95. expectedURL: fmt.Sprintf("https://[::1]:%s", portStr),
  96. },
  97. }
  98. for _, test := range tests {
  99. url := GetClientURLByIP(test.ip)
  100. if url != test.expectedURL {
  101. t.Errorf("expected %s, got %s", test.expectedURL, url)
  102. }
  103. }
  104. }
  105. func TestGetEtcdEndpointsWithBackoff(t *testing.T) {
  106. var tests = []struct {
  107. name string
  108. pods []testresources.FakeStaticPod
  109. configMap *testresources.FakeConfigMap
  110. expectedEndpoints []string
  111. expectedErr bool
  112. }{
  113. {
  114. name: "no pod annotations; no ClusterStatus",
  115. expectedEndpoints: []string{},
  116. },
  117. {
  118. name: "ipv4 endpoint in pod annotation; no ClusterStatus; port is preserved",
  119. pods: []testresources.FakeStaticPod{
  120. {
  121. Component: constants.Etcd,
  122. Annotations: map[string]string{
  123. constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.4:1234",
  124. },
  125. },
  126. },
  127. expectedEndpoints: []string{"https://1.2.3.4:1234"},
  128. },
  129. {
  130. name: "no pod annotations; ClusterStatus with valid ipv4 endpoint; port is inferred",
  131. configMap: testresources.ClusterStatusWithAPIEndpoint("cp-0", kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234}),
  132. expectedEndpoints: []string{"https://1.2.3.4:2379"},
  133. },
  134. }
  135. for _, rt := range tests {
  136. t.Run(rt.name, func(t *testing.T) {
  137. client := clientsetfake.NewSimpleClientset()
  138. for _, pod := range rt.pods {
  139. if err := pod.Create(client); err != nil {
  140. t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
  141. }
  142. }
  143. if rt.configMap != nil {
  144. if err := rt.configMap.Create(client); err != nil {
  145. t.Error("could not create ConfigMap")
  146. }
  147. }
  148. endpoints, err := getEtcdEndpointsWithBackoff(client, wait.Backoff{Duration: 0, Jitter: 0, Steps: 1})
  149. if err != nil && !rt.expectedErr {
  150. t.Errorf("got error %q; was expecting no errors", err)
  151. return
  152. } else if err == nil && rt.expectedErr {
  153. t.Error("got no error; was expecting an error")
  154. return
  155. } else if err != nil && rt.expectedErr {
  156. return
  157. }
  158. if !reflect.DeepEqual(endpoints, rt.expectedEndpoints) {
  159. t.Errorf("expected etcd endpoints: %v; got: %v", rt.expectedEndpoints, endpoints)
  160. }
  161. })
  162. }
  163. }
  164. func TestGetRawEtcdEndpointsFromPodAnnotation(t *testing.T) {
  165. var tests = []struct {
  166. name string
  167. pods []testresources.FakeStaticPod
  168. clientSetup func(*clientsetfake.Clientset)
  169. expectedEndpoints []string
  170. expectedErr bool
  171. }{
  172. {
  173. name: "exactly one pod with annotation",
  174. pods: []testresources.FakeStaticPod{
  175. {
  176. NodeName: "cp-0",
  177. Component: constants.Etcd,
  178. Annotations: map[string]string{constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.4:2379"},
  179. },
  180. },
  181. expectedEndpoints: []string{"https://1.2.3.4:2379"},
  182. },
  183. {
  184. name: "no pods with annotation",
  185. expectedErr: true,
  186. },
  187. {
  188. name: "exactly one pod with annotation; all requests fail",
  189. pods: []testresources.FakeStaticPod{
  190. {
  191. NodeName: "cp-0",
  192. Component: constants.Etcd,
  193. Annotations: map[string]string{constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.4:2379"},
  194. },
  195. },
  196. clientSetup: func(clientset *clientsetfake.Clientset) {
  197. clientset.PrependReactor("list", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  198. return true, nil, apierrors.NewInternalError(errors.New("API server down"))
  199. })
  200. },
  201. expectedErr: true,
  202. },
  203. }
  204. for _, rt := range tests {
  205. t.Run(rt.name, func(t *testing.T) {
  206. client := clientsetfake.NewSimpleClientset()
  207. for i, pod := range rt.pods {
  208. if err := pod.CreateWithPodSuffix(client, strconv.Itoa(i)); err != nil {
  209. t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
  210. }
  211. }
  212. if rt.clientSetup != nil {
  213. rt.clientSetup(client)
  214. }
  215. endpoints, err := getRawEtcdEndpointsFromPodAnnotation(client, wait.Backoff{Duration: 0, Jitter: 0, Steps: 1})
  216. if err != nil && !rt.expectedErr {
  217. t.Errorf("got error %v, but wasn't expecting any error", err)
  218. return
  219. } else if err == nil && rt.expectedErr {
  220. t.Error("didn't get any error; but was expecting an error")
  221. return
  222. } else if err != nil && rt.expectedErr {
  223. return
  224. }
  225. if !reflect.DeepEqual(endpoints, rt.expectedEndpoints) {
  226. t.Errorf("expected etcd endpoints: %v; got: %v", rt.expectedEndpoints, endpoints)
  227. }
  228. })
  229. }
  230. }
  231. func TestGetRawEtcdEndpointsFromPodAnnotationWithoutRetry(t *testing.T) {
  232. var tests = []struct {
  233. name string
  234. pods []testresources.FakeStaticPod
  235. clientSetup func(*clientsetfake.Clientset)
  236. expectedEndpoints []string
  237. expectedErr bool
  238. }{
  239. {
  240. name: "no pods",
  241. expectedEndpoints: []string{},
  242. },
  243. {
  244. name: "exactly one pod with annotation",
  245. pods: []testresources.FakeStaticPod{
  246. {
  247. NodeName: "cp-0",
  248. Component: constants.Etcd,
  249. Annotations: map[string]string{constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.4:2379"},
  250. },
  251. },
  252. expectedEndpoints: []string{"https://1.2.3.4:2379"},
  253. },
  254. {
  255. name: "two pods with annotation",
  256. pods: []testresources.FakeStaticPod{
  257. {
  258. NodeName: "cp-0",
  259. Component: constants.Etcd,
  260. Annotations: map[string]string{constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.4:2379"},
  261. },
  262. {
  263. NodeName: "cp-1",
  264. Component: constants.Etcd,
  265. Annotations: map[string]string{constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.5:2379"},
  266. },
  267. },
  268. expectedEndpoints: []string{"https://1.2.3.4:2379", "https://1.2.3.5:2379"},
  269. },
  270. {
  271. name: "exactly one pod with annotation; request fails",
  272. pods: []testresources.FakeStaticPod{
  273. {
  274. NodeName: "cp-0",
  275. Component: constants.Etcd,
  276. Annotations: map[string]string{constants.EtcdAdvertiseClientUrlsAnnotationKey: "https://1.2.3.4:2379"},
  277. },
  278. },
  279. clientSetup: func(clientset *clientsetfake.Clientset) {
  280. clientset.PrependReactor("list", "pods", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) {
  281. return true, nil, apierrors.NewInternalError(errors.New("API server down"))
  282. })
  283. },
  284. expectedErr: true,
  285. },
  286. }
  287. for _, rt := range tests {
  288. t.Run(rt.name, func(t *testing.T) {
  289. client := clientsetfake.NewSimpleClientset()
  290. for _, pod := range rt.pods {
  291. if err := pod.Create(client); err != nil {
  292. t.Errorf("error setting up test creating pod for node %q", pod.NodeName)
  293. return
  294. }
  295. }
  296. if rt.clientSetup != nil {
  297. rt.clientSetup(client)
  298. }
  299. endpoints, _, err := getRawEtcdEndpointsFromPodAnnotationWithoutRetry(client)
  300. if err != nil && !rt.expectedErr {
  301. t.Errorf("got error %v, but wasn't expecting any error", err)
  302. return
  303. } else if err == nil && rt.expectedErr {
  304. t.Error("didn't get any error; but was expecting an error")
  305. return
  306. } else if err != nil && rt.expectedErr {
  307. return
  308. }
  309. if !reflect.DeepEqual(endpoints, rt.expectedEndpoints) {
  310. t.Errorf("expected etcd endpoints: %v; got: %v", rt.expectedEndpoints, endpoints)
  311. }
  312. })
  313. }
  314. }