github.com/alloyci/alloy-runner@v1.0.1-0.20180222164613-925503ccafd6/executors/kubernetes/util_test.go (about) 1 package kubernetes 2 3 import ( 4 "fmt" 5 "net/http" 6 "reflect" 7 "strings" 8 "testing" 9 10 "golang.org/x/net/context" 11 "k8s.io/kubernetes/pkg/api" 12 "k8s.io/kubernetes/pkg/api/testapi" 13 "k8s.io/kubernetes/pkg/api/unversioned" 14 "k8s.io/kubernetes/pkg/client/restclient" 15 client "k8s.io/kubernetes/pkg/client/unversioned" 16 "k8s.io/kubernetes/pkg/client/unversioned/fake" 17 18 "gitlab.com/gitlab-org/gitlab-runner/common" 19 ) 20 21 func TestGetKubeClientConfig(t *testing.T) { 22 originalInClusterConfig := inClusterConfig 23 originalDefaultKubectlConfig := defaultKubectlConfig 24 defer func() { 25 inClusterConfig = originalInClusterConfig 26 defaultKubectlConfig = originalDefaultKubectlConfig 27 }() 28 29 completeConfig := &restclient.Config{ 30 Host: "host", 31 BearerToken: "token", 32 TLSClientConfig: restclient.TLSClientConfig{ 33 CAFile: "ca", 34 }, 35 } 36 37 noConfigAvailable := func() (*restclient.Config, error) { 38 return nil, fmt.Errorf("config not available") 39 } 40 41 aConfig := func() (*restclient.Config, error) { 42 config := *completeConfig 43 return &config, nil 44 45 } 46 47 tests := []struct { 48 name string 49 config *common.KubernetesConfig 50 overwrites *overwrites 51 inClusterConfig kubeConfigProvider 52 defaultKubectlConfig kubeConfigProvider 53 error bool 54 expected *restclient.Config 55 }{ 56 { 57 name: "Incomplete cert based auth outside cluster", 58 config: &common.KubernetesConfig{ 59 Host: "host", 60 CertFile: "test", 61 }, 62 inClusterConfig: noConfigAvailable, 63 defaultKubectlConfig: noConfigAvailable, 64 overwrites: &overwrites{}, 65 error: true, 66 }, 67 { 68 name: "Complete cert based auth take precendece over in cluster config", 69 config: &common.KubernetesConfig{ 70 CertFile: "crt", 71 KeyFile: "key", 72 CAFile: "ca", 73 Host: "another_host", 74 }, 75 overwrites: &overwrites{}, 76 inClusterConfig: aConfig, 77 defaultKubectlConfig: aConfig, 78 expected: &restclient.Config{ 79 Host: "another_host", 80 TLSClientConfig: restclient.TLSClientConfig{ 81 CertFile: "crt", 82 KeyFile: "key", 83 CAFile: "ca", 84 }, 85 }, 86 }, 87 { 88 name: "User provided configuration take precedence", 89 config: &common.KubernetesConfig{ 90 Host: "another_host", 91 CAFile: "ca", 92 }, 93 overwrites: &overwrites{ 94 bearerToken: "another_token", 95 }, 96 inClusterConfig: aConfig, 97 defaultKubectlConfig: aConfig, 98 expected: &restclient.Config{ 99 Host: "another_host", 100 BearerToken: "another_token", 101 TLSClientConfig: restclient.TLSClientConfig{ 102 CAFile: "ca", 103 }, 104 }, 105 }, 106 { 107 name: "InCluster config", 108 config: &common.KubernetesConfig{}, 109 overwrites: &overwrites{}, 110 inClusterConfig: aConfig, 111 defaultKubectlConfig: noConfigAvailable, 112 expected: completeConfig, 113 }, 114 { 115 name: "Default cluster config", 116 config: &common.KubernetesConfig{}, 117 overwrites: &overwrites{}, 118 inClusterConfig: noConfigAvailable, 119 defaultKubectlConfig: aConfig, 120 expected: completeConfig, 121 }, 122 { 123 name: "Overwrites works also in cluster", 124 config: &common.KubernetesConfig{}, 125 overwrites: &overwrites{ 126 bearerToken: "bearerToken", 127 }, 128 inClusterConfig: aConfig, 129 defaultKubectlConfig: noConfigAvailable, 130 expected: &restclient.Config{ 131 Host: "host", 132 BearerToken: "bearerToken", 133 TLSClientConfig: restclient.TLSClientConfig{ 134 CAFile: "ca", 135 }, 136 }, 137 }, 138 } 139 for _, test := range tests { 140 t.Run(test.name, func(t *testing.T) { 141 inClusterConfig = test.inClusterConfig 142 defaultKubectlConfig = test.defaultKubectlConfig 143 144 rcConf, err := getKubeClientConfig(test.config, test.overwrites) 145 146 if err != nil && !test.error { 147 t.Errorf("expected error, but instead received: %v", rcConf) 148 return 149 } 150 151 if !reflect.DeepEqual(rcConf, test.expected) { 152 t.Errorf("expected: '%v', got: '%v'", test.expected, rcConf) 153 } 154 }) 155 } 156 } 157 158 func TestWaitForPodRunning(t *testing.T) { 159 version := testapi.Default.GroupVersion().Version 160 codec := testapi.Default.Codec() 161 retries := 0 162 163 tests := []struct { 164 Name string 165 Pod *api.Pod 166 Config *common.KubernetesConfig 167 ClientFunc func(*http.Request) (*http.Response, error) 168 PodEndPhase api.PodPhase 169 Retries int 170 Error bool 171 ExactRetries bool 172 }{ 173 { 174 Name: "ensure function retries until ready", 175 Pod: &api.Pod{ 176 ObjectMeta: api.ObjectMeta{ 177 Name: "test-pod", 178 Namespace: "test-ns", 179 }, 180 }, 181 Config: &common.KubernetesConfig{}, 182 ClientFunc: func(req *http.Request) (*http.Response, error) { 183 switch p, m := req.URL.Path, req.Method; { 184 case p == "/api/"+version+"/namespaces/test-ns/pods/test-pod" && m == "GET": 185 pod := &api.Pod{ 186 ObjectMeta: api.ObjectMeta{ 187 Name: "test-pod", 188 Namespace: "test-ns", 189 }, 190 Status: api.PodStatus{ 191 Phase: api.PodPending, 192 }, 193 } 194 195 if retries > 1 { 196 pod.Status.Phase = api.PodRunning 197 pod.Status.ContainerStatuses = []api.ContainerStatus{ 198 { 199 Ready: false, 200 }, 201 } 202 } 203 204 if retries > 2 { 205 pod.Status.Phase = api.PodRunning 206 pod.Status.ContainerStatuses = []api.ContainerStatus{ 207 { 208 Ready: true, 209 }, 210 } 211 } 212 retries++ 213 return &http.Response{StatusCode: http.StatusOK, Body: objBody(codec, pod), Header: map[string][]string{ 214 "Content-Type": []string{"application/json"}, 215 }}, nil 216 default: 217 // Ensures no GET is performed when deleting by name 218 t.Errorf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) 219 return nil, fmt.Errorf("unexpected request") 220 } 221 }, 222 PodEndPhase: api.PodRunning, 223 Retries: 2, 224 }, 225 { 226 Name: "ensure function errors if pod already succeeded", 227 Pod: &api.Pod{ 228 ObjectMeta: api.ObjectMeta{ 229 Name: "test-pod", 230 Namespace: "test-ns", 231 }, 232 }, 233 Config: &common.KubernetesConfig{}, 234 ClientFunc: func(req *http.Request) (*http.Response, error) { 235 switch p, m := req.URL.Path, req.Method; { 236 case p == "/api/"+version+"/namespaces/test-ns/pods/test-pod" && m == "GET": 237 pod := &api.Pod{ 238 ObjectMeta: api.ObjectMeta{ 239 Name: "test-pod", 240 Namespace: "test-ns", 241 }, 242 Status: api.PodStatus{ 243 Phase: api.PodSucceeded, 244 }, 245 } 246 return &http.Response{StatusCode: http.StatusOK, Body: objBody(codec, pod), Header: map[string][]string{ 247 "Content-Type": []string{"application/json"}, 248 }}, nil 249 default: 250 // Ensures no GET is performed when deleting by name 251 t.Errorf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) 252 return nil, fmt.Errorf("unexpected request") 253 } 254 }, 255 Error: true, 256 PodEndPhase: api.PodSucceeded, 257 }, 258 { 259 Name: "ensure function returns error if pod unknown", 260 Pod: &api.Pod{ 261 ObjectMeta: api.ObjectMeta{ 262 Name: "test-pod", 263 Namespace: "test-ns", 264 }, 265 }, 266 Config: &common.KubernetesConfig{}, 267 ClientFunc: func(req *http.Request) (*http.Response, error) { 268 return nil, fmt.Errorf("error getting pod") 269 }, 270 PodEndPhase: api.PodUnknown, 271 Error: true, 272 }, 273 { 274 Name: "ensure poll parameters work correctly", 275 Pod: &api.Pod{ 276 ObjectMeta: api.ObjectMeta{ 277 Name: "test-pod", 278 Namespace: "test-ns", 279 }, 280 }, 281 // Will result in 3 attempts at 0, 3, and 6 seconds 282 Config: &common.KubernetesConfig{ 283 PollInterval: 0, // Should get changed to default of 3 by GetPollInterval() 284 PollTimeout: 6, 285 }, 286 ClientFunc: func(req *http.Request) (*http.Response, error) { 287 switch p, m := req.URL.Path, req.Method; { 288 case p == "/api/"+version+"/namespaces/test-ns/pods/test-pod" && m == "GET": 289 pod := &api.Pod{ 290 ObjectMeta: api.ObjectMeta{ 291 Name: "test-pod", 292 Namespace: "test-ns", 293 }, 294 } 295 if retries > 3 { 296 t.Errorf("Too many retries for the given poll parameters. (Expected 3)") 297 } 298 retries++ 299 return &http.Response{StatusCode: http.StatusOK, Body: objBody(codec, pod), Header: map[string][]string{ 300 "Content-Type": []string{"application/json"}, 301 }}, nil 302 default: 303 // Ensures no GET is performed when deleting by name 304 t.Errorf("unexpected request: %s %#v\n%#v", req.Method, req.URL, req) 305 return nil, fmt.Errorf("unexpected request") 306 } 307 }, 308 PodEndPhase: api.PodUnknown, 309 Retries: 3, 310 Error: true, 311 ExactRetries: true, 312 }, 313 } 314 315 for _, test := range tests { 316 t.Run(test.Name, func(t *testing.T) { 317 retries = 0 318 c := client.NewOrDie(&restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &unversioned.GroupVersion{Version: version}}}) 319 fakeClient := fake.RESTClient{ 320 Codec: codec, 321 Client: fake.CreateHTTPClient(test.ClientFunc), 322 } 323 c.Client = fakeClient.Client 324 fw := testWriter{ 325 call: func(b []byte) (int, error) { 326 if retries < test.Retries { 327 if !strings.Contains(string(b), "Waiting for pod") { 328 t.Errorf("[%s] Expected to continue waiting for pod. Got: '%s'", test.Name, string(b)) 329 } 330 } 331 return len(b), nil 332 }, 333 } 334 phase, err := waitForPodRunning(context.Background(), c, test.Pod, fw, test.Config) 335 336 if err != nil && !test.Error { 337 t.Errorf("[%s] Expected success. Got: %s", test.Name, err.Error()) 338 return 339 } 340 341 if phase != test.PodEndPhase { 342 t.Errorf("[%s] Invalid end state. Expected '%v', got: '%v'", test.Name, test.PodEndPhase, phase) 343 return 344 } 345 346 if test.ExactRetries && retries < test.Retries { 347 t.Errorf("[%s] Not enough retries. Expected: %d, got: %d", test.Name, test.Retries, retries) 348 return 349 } 350 }) 351 } 352 } 353 354 type testWriter struct { 355 call func([]byte) (int, error) 356 } 357 358 func (t testWriter) Write(b []byte) (int, error) { 359 return t.call(b) 360 }