github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/portforward/pod_forwarder_test.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package portforward 18 19 import ( 20 "context" 21 "io/ioutil" 22 "reflect" 23 "testing" 24 "time" 25 26 v1 "k8s.io/api/core/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/util/wait" 29 "k8s.io/apimachinery/pkg/watch" 30 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 33 schemautil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/util" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 35 "github.com/GoogleContainerTools/skaffold/testutil" 36 testEvent "github.com/GoogleContainerTools/skaffold/testutil/event" 37 ) 38 39 func TestAutomaticPortForwardPod(t *testing.T) { 40 tests := []struct { 41 description string 42 pods []*v1.Pod 43 forwarder *testForwarder 44 availablePorts []int 45 expectedPorts []int 46 expectedEntries map[string]*portForwardEntry 47 shouldErr bool 48 }{ 49 { 50 description: "single container port", 51 availablePorts: []int{8080}, 52 expectedPorts: []int{8080}, 53 expectedEntries: map[string]*portForwardEntry{ 54 "owner-containername-namespace-portname-8080": { 55 resourceVersion: 1, 56 podName: "podname", 57 containerName: "containername", 58 resource: latest.PortForwardResource{ 59 Type: "pod", 60 Name: "podname", 61 Namespace: "namespace", 62 Port: schemautil.FromInt(8080), 63 Address: "127.0.0.1", 64 LocalPort: 0, 65 }, 66 ownerReference: "owner", 67 automaticPodForwarding: true, 68 portName: "portname", 69 localPort: 8080, 70 }, 71 }, 72 pods: []*v1.Pod{ 73 { 74 ObjectMeta: metav1.ObjectMeta{ 75 Name: "podname", 76 ResourceVersion: "1", 77 Namespace: "namespace", 78 }, 79 Spec: v1.PodSpec{ 80 Containers: []v1.Container{ 81 { 82 Name: "containername", 83 Ports: []v1.ContainerPort{ 84 { 85 ContainerPort: 8080, 86 Name: "portname", 87 }, 88 }, 89 }, 90 }, 91 }, 92 }, 93 }, 94 }, 95 { 96 description: "unavailable container port", 97 availablePorts: []int{9000}, 98 expectedPorts: []int{9000}, 99 expectedEntries: map[string]*portForwardEntry{ 100 "owner-containername-namespace-portname-8080": { 101 resourceVersion: 1, 102 podName: "podname", 103 resource: latest.PortForwardResource{ 104 Type: "pod", 105 Name: "podname", 106 Namespace: "namespace", 107 Port: schemautil.FromInt(8080), 108 Address: "127.0.0.1", 109 LocalPort: 0, 110 }, 111 ownerReference: "owner", 112 automaticPodForwarding: true, 113 containerName: "containername", 114 portName: "portname", 115 localPort: 9000, 116 }, 117 }, 118 pods: []*v1.Pod{ 119 { 120 ObjectMeta: metav1.ObjectMeta{ 121 Name: "podname", 122 ResourceVersion: "1", 123 Namespace: "namespace", 124 }, 125 Spec: v1.PodSpec{ 126 Containers: []v1.Container{ 127 { 128 Name: "containername", 129 Ports: []v1.ContainerPort{ 130 { 131 ContainerPort: 8080, 132 Name: "portname", 133 }, 134 }, 135 }, 136 }, 137 }, 138 }, 139 }, 140 }, 141 { 142 description: "bad resource version", 143 availablePorts: []int{8080}, 144 expectedPorts: nil, 145 shouldErr: true, 146 expectedEntries: nil, 147 pods: []*v1.Pod{ 148 { 149 ObjectMeta: metav1.ObjectMeta{ 150 Name: "podname", 151 ResourceVersion: "10000000000a", 152 Namespace: "namespace", 153 }, 154 Spec: v1.PodSpec{ 155 Containers: []v1.Container{ 156 { 157 Name: "containername", 158 Ports: []v1.ContainerPort{ 159 { 160 ContainerPort: 8080, 161 Name: "portname", 162 }, 163 }, 164 }, 165 }, 166 }, 167 }, 168 }, 169 }, 170 { 171 description: "two different container ports", 172 availablePorts: []int{8080, 50051}, 173 expectedPorts: []int{8080, 50051}, 174 expectedEntries: map[string]*portForwardEntry{ 175 "owner-containername-namespace-portname-8080": { 176 resourceVersion: 1, 177 podName: "podname", 178 containerName: "containername", 179 resource: latest.PortForwardResource{ 180 Type: "pod", 181 Name: "podname", 182 Namespace: "namespace", 183 Port: schemautil.FromInt(8080), 184 Address: "127.0.0.1", 185 LocalPort: 0, 186 }, 187 ownerReference: "owner", 188 portName: "portname", 189 automaticPodForwarding: true, 190 localPort: 8080, 191 }, 192 "owner-containername2-namespace2-portname2-50051": { 193 resourceVersion: 1, 194 podName: "podname2", 195 containerName: "containername2", 196 resource: latest.PortForwardResource{ 197 Type: "pod", 198 Name: "podname2", 199 Namespace: "namespace2", 200 Port: schemautil.FromInt(50051), 201 Address: "127.0.0.1", 202 LocalPort: 0, 203 }, 204 ownerReference: "owner", 205 portName: "portname2", 206 automaticPodForwarding: true, 207 localPort: 50051, 208 }, 209 }, 210 pods: []*v1.Pod{ 211 { 212 ObjectMeta: metav1.ObjectMeta{ 213 Name: "podname", 214 ResourceVersion: "1", 215 Namespace: "namespace", 216 }, 217 Spec: v1.PodSpec{ 218 Containers: []v1.Container{ 219 { 220 Name: "containername", 221 Ports: []v1.ContainerPort{ 222 { 223 ContainerPort: 8080, 224 Name: "portname", 225 }, 226 }, 227 }, 228 }, 229 }, 230 }, 231 { 232 ObjectMeta: metav1.ObjectMeta{ 233 Name: "podname2", 234 ResourceVersion: "1", 235 Namespace: "namespace2", 236 }, 237 Spec: v1.PodSpec{ 238 Containers: []v1.Container{ 239 { 240 Name: "containername2", 241 Ports: []v1.ContainerPort{ 242 { 243 ContainerPort: 50051, 244 Name: "portname2", 245 }, 246 }, 247 }, 248 }, 249 }, 250 }, 251 }, 252 }, 253 { 254 description: "two same container ports", 255 availablePorts: []int{8080, 9000}, 256 expectedPorts: []int{8080, 9000}, 257 expectedEntries: map[string]*portForwardEntry{ 258 "owner-containername-namespace-portname-8080": { 259 resourceVersion: 1, 260 podName: "podname", 261 containerName: "containername", 262 portName: "portname", 263 resource: latest.PortForwardResource{ 264 Type: "pod", 265 Name: "podname", 266 Namespace: "namespace", 267 Port: schemautil.FromInt(8080), 268 Address: "127.0.0.1", 269 LocalPort: 0, 270 }, 271 ownerReference: "owner", 272 automaticPodForwarding: true, 273 localPort: 8080, 274 }, 275 "owner-containername2-namespace2-portname2-8080": { 276 resourceVersion: 1, 277 podName: "podname2", 278 containerName: "containername2", 279 portName: "portname2", 280 resource: latest.PortForwardResource{ 281 Type: "pod", 282 Name: "podname2", 283 Namespace: "namespace2", 284 Port: schemautil.FromInt(8080), 285 Address: "127.0.0.1", 286 LocalPort: 0, 287 }, 288 ownerReference: "owner", 289 automaticPodForwarding: true, 290 localPort: 9000, 291 }, 292 }, 293 pods: []*v1.Pod{ 294 { 295 ObjectMeta: metav1.ObjectMeta{ 296 Name: "podname", 297 ResourceVersion: "1", 298 Namespace: "namespace", 299 }, 300 Spec: v1.PodSpec{ 301 Containers: []v1.Container{ 302 { 303 Name: "containername", 304 Ports: []v1.ContainerPort{ 305 { 306 ContainerPort: 8080, 307 Name: "portname", 308 }, 309 }, 310 }, 311 }, 312 }, 313 }, 314 { 315 ObjectMeta: metav1.ObjectMeta{ 316 Name: "podname2", 317 ResourceVersion: "1", 318 Namespace: "namespace2", 319 }, 320 Spec: v1.PodSpec{ 321 Containers: []v1.Container{ 322 { 323 Name: "containername2", 324 Ports: []v1.ContainerPort{ 325 { 326 ContainerPort: 8080, 327 Name: "portname2", 328 }, 329 }, 330 }, 331 }, 332 }, 333 }, 334 }, 335 }, 336 { 337 description: "updated pod gets port forwarded", 338 availablePorts: []int{8080}, 339 expectedPorts: []int{8080}, 340 expectedEntries: map[string]*portForwardEntry{ 341 "owner-containername-namespace-portname-8080": { 342 resourceVersion: 2, 343 podName: "podname", 344 containerName: "containername", 345 portName: "portname", 346 resource: latest.PortForwardResource{ 347 Type: "pod", 348 Name: "podname", 349 Namespace: "namespace", 350 Port: schemautil.FromInt(8080), 351 Address: "127.0.0.1", 352 LocalPort: 0, 353 }, 354 ownerReference: "owner", 355 automaticPodForwarding: true, 356 localPort: 8080, 357 }, 358 }, 359 pods: []*v1.Pod{ 360 { 361 ObjectMeta: metav1.ObjectMeta{ 362 Name: "podname", 363 ResourceVersion: "1", 364 Namespace: "namespace", 365 }, 366 Spec: v1.PodSpec{ 367 Containers: []v1.Container{ 368 { 369 Name: "containername", 370 Ports: []v1.ContainerPort{ 371 { 372 ContainerPort: 8080, 373 Name: "portname", 374 }, 375 }, 376 }, 377 }, 378 }, 379 }, 380 { 381 ObjectMeta: metav1.ObjectMeta{ 382 Name: "podname", 383 ResourceVersion: "2", 384 Namespace: "namespace", 385 }, 386 Spec: v1.PodSpec{ 387 Containers: []v1.Container{ 388 { 389 Name: "containername", 390 Ports: []v1.ContainerPort{ 391 { 392 ContainerPort: 8080, 393 Name: "portname", 394 }, 395 }, 396 }, 397 }, 398 }, 399 }, 400 }, 401 }, 402 } 403 for _, test := range tests { 404 testutil.Run(t, test.description, func(t *testutil.T) { 405 testEvent.InitializeState([]latest.Pipeline{{}}) 406 taken := map[int]struct{}{} 407 t.Override(&retrieveAvailablePort, mockRetrieveAvailablePort(util.Loopback, taken, test.availablePorts)) 408 t.Override(&topLevelOwnerKey, func(context.Context, metav1.Object, string, string) string { return "owner" }) 409 410 if test.forwarder == nil { 411 test.forwarder = newTestForwarder() 412 } 413 entryManager := NewEntryManager(nil) 414 entryManager.entryForwarder = test.forwarder 415 416 p := NewWatchingPodForwarder(entryManager, "", kubernetes.NewImageList(), allPorts) 417 p.Start(context.Background(), ioutil.Discard, nil) 418 for _, pod := range test.pods { 419 err := p.portForwardPod(context.Background(), pod) 420 t.CheckError(test.shouldErr, err) 421 } 422 423 t.CheckDeepEqual(test.expectedPorts, test.forwarder.forwardedPorts.List()) 424 425 // cmp.Diff cannot access unexported fields, so use reflect.DeepEqual here directly 426 for k, v := range test.expectedEntries { 427 if frv, found := test.forwarder.forwardedResources.Load(k); !found { 428 t.Errorf("Forwarded entries missing key %v, value %v", k, v) 429 } else if !reflect.DeepEqual(v, frv) { 430 t.Errorf("Forwarded entries mismatch for key %v: Expected %v, Actual %v", k, v, frv) 431 } 432 } 433 }) 434 } 435 } 436 437 func TestStartPodForwarder(t *testing.T) { 438 pod := &v1.Pod{ 439 ObjectMeta: metav1.ObjectMeta{ 440 Namespace: "default", 441 ResourceVersion: "9", 442 }, 443 Spec: v1.PodSpec{ 444 Containers: []v1.Container{{ 445 Name: "mycontainer", 446 Image: "image", 447 Ports: []v1.ContainerPort{{ 448 Name: "myport", 449 ContainerPort: 8080, 450 }}, 451 }}, 452 }, 453 Status: v1.PodStatus{ 454 Phase: v1.PodRunning, 455 }, 456 } 457 458 tests := []struct { 459 description string 460 entryExpected bool 461 event kubernetes.PodEvent 462 }{ 463 { 464 description: "pod modified event", 465 entryExpected: true, 466 event: kubernetes.PodEvent{ 467 Type: watch.Modified, 468 Pod: pod, 469 }, 470 }, 471 { 472 description: "event is deleted", 473 event: kubernetes.PodEvent{ 474 Type: watch.Deleted, 475 Pod: pod, 476 }, 477 }, 478 } 479 480 for _, test := range tests { 481 testutil.Run(t, test.description, func(t *testutil.T) { 482 testEvent.InitializeState([]latest.Pipeline{{}}) 483 t.Override(&topLevelOwnerKey, func(context.Context, metav1.Object, string, string) string { return "owner" }) 484 t.Override(&newPodWatcher, func(kubernetes.PodSelector) kubernetes.PodWatcher { 485 return &fakePodWatcher{ 486 events: []kubernetes.PodEvent{test.event}, 487 } 488 }) 489 490 imageList := kubernetes.NewImageList() 491 imageList.Add("image") 492 493 fakeForwarder := newTestForwarder() 494 entryManager := NewEntryManager(fakeForwarder) 495 496 p := NewWatchingPodForwarder(entryManager, "", imageList, allPorts) 497 p.Start(context.Background(), ioutil.Discard, nil) 498 499 // wait for the pod resource to be forwarded 500 err := wait.PollImmediate(10*time.Millisecond, 100*time.Millisecond, func() (bool, error) { 501 _, ok := fakeForwarder.forwardedResources.Load("owner-mycontainer-default-myport-8080") 502 return ok, nil 503 }) 504 if err != nil && test.entryExpected { 505 t.Fatalf("expected entry wasn't forwarded: %v", err) 506 } 507 }) 508 } 509 } 510 511 type fakePodWatcher struct { 512 events []kubernetes.PodEvent 513 receiver chan<- kubernetes.PodEvent 514 } 515 516 func (f *fakePodWatcher) Register(receiver chan<- kubernetes.PodEvent) { 517 f.receiver = receiver 518 } 519 520 func (f *fakePodWatcher) Deregister(_ chan<- kubernetes.PodEvent) {} // noop 521 522 func (f *fakePodWatcher) Start(_ context.Context, _ string, _ []string) (func(), error) { 523 go func() { 524 for _, event := range f.events { 525 f.receiver <- event 526 } 527 }() 528 529 return func() {}, nil 530 }