k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cluster/gce/windows/smoke-test.sh (about) 1 #!/bin/bash 2 3 # Copyright 2019 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 # A small smoke test to run against a just-deployed kube-up cluster with Windows 18 # nodes. Performs checks such as: 19 # 1) Verifying that all Windows nodes have status Ready. 20 # 2) Verifying that no system pods are attempting to run on Windows nodes. 21 # 3) Verifying pairwise connectivity between most of the following: Linux 22 # pods, Windows pods, K8s services, and the Internet. 23 # 4) Verifying that basic DNS resolution works in Windows pods. 24 # 25 # This script assumes that it is run from the root of the kubernetes repository. 26 # 27 # TODOs: 28 # - Implement the node-to-pod checks. 29 # - Capture stdout for each command to a file and only print it when the test 30 # fails. 31 # - Move copy-pasted code into reusable functions. 32 # - Continue running all checks after one fails. 33 # - Test service connectivity by running a test pod with an http server and 34 # exposing it as a service (rather than curl-ing from existing system 35 # services that don't serve http requests). 36 # - Add test retries for transient errors, such as: 37 # "error: unable to upgrade connection: Authorization error 38 # (user=kube-apiserver, verb=create, resource=nodes, subresource=proxy)" 39 40 # Override this to use a different kubectl binary. 41 kubectl=kubectl 42 linux_deployment_timeout=60 43 windows_deployment_timeout=600 44 output_file=/tmp/k8s-smoke-test.out 45 46 function check_windows_nodes_are_ready { 47 # kubectl filtering is the worst. 48 statuses=$(${kubectl} get nodes -l kubernetes.io/os=windows \ 49 -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}') 50 for status in $statuses; do 51 if [[ $status == "False" ]]; then 52 echo "ERROR: some Windows node has status != Ready" 53 echo "kubectl get nodes -l kubernetes.io/os=windows" 54 ${kubectl} get nodes -l kubernetes.io/os=windows 55 exit 1 56 fi 57 done 58 echo "Verified that all Windows nodes have status Ready" 59 } 60 61 function untaint_windows_nodes { 62 # Untaint the windows nodes to allow test workloads without tolerations to be 63 # scheduled onto them. 64 WINDOWS_NODES=$(${kubectl} get nodes -l kubernetes.io/os=windows -o name) 65 for node in $WINDOWS_NODES; do 66 ${kubectl} taint node "$node" node.kubernetes.io/os:NoSchedule- 67 done 68 } 69 70 function check_no_system_pods_on_windows_nodes { 71 windows_system_pods=$(${kubectl} get pods --namespace kube-system \ 72 -o wide | grep -E "Pending|windows" | wc -w) 73 if [[ $windows_system_pods -ne 0 ]]; then 74 echo "ERROR: there are kube-system pods trying to run on Windows nodes" 75 echo "kubectl get pods --namespace kube-system -o wide" 76 ${kubectl} get pods --namespace kube-system -o wide 77 exit 1 78 fi 79 echo "Verified that all system pods are running on Linux nodes" 80 } 81 82 linux_webserver_deployment=linux-nginx 83 linux_webserver_pod_label=nginx 84 linux_webserver_replicas=1 85 86 function deploy_linux_webserver_pod { 87 echo "Writing example deployment to $linux_webserver_deployment.yaml" 88 cat <<EOF > $linux_webserver_deployment.yaml 89 apiVersion: apps/v1 90 kind: Deployment 91 metadata: 92 name: $linux_webserver_deployment 93 labels: 94 app: $linux_webserver_pod_label 95 spec: 96 replicas: $linux_webserver_replicas 97 selector: 98 matchLabels: 99 app: $linux_webserver_pod_label 100 template: 101 metadata: 102 labels: 103 app: $linux_webserver_pod_label 104 spec: 105 containers: 106 - name: nginx 107 image: nginx:1.7.9 108 nodeSelector: 109 kubernetes.io/os: linux 110 EOF 111 112 if ! ${kubectl} create -f $linux_webserver_deployment.yaml; then 113 echo "kubectl create -f $linux_webserver_deployment.yaml failed" 114 exit 1 115 fi 116 117 timeout=$linux_deployment_timeout 118 while [[ $timeout -gt 0 ]]; do 119 echo "Waiting for $linux_webserver_replicas Linux $linux_webserver_pod_label pods to become Ready" 120 statuses=$(${kubectl} get pods -l app=$linux_webserver_pod_label \ 121 -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' \ 122 | grep "True" | wc -w) 123 if [[ $statuses -eq $linux_webserver_replicas ]]; then 124 break 125 else 126 sleep 10 127 (( timeout=timeout-10 )) 128 fi 129 done 130 131 if [[ $timeout -gt 0 ]]; then 132 echo "All $linux_webserver_pod_label pods became Ready" 133 else 134 echo "ERROR: Not all $linux_webserver_pod_label pods became Ready" 135 echo "kubectl get pods -l app=$linux_webserver_pod_label" 136 ${kubectl} get pods -l app=$linux_webserver_pod_label 137 cleanup_deployments 138 exit 1 139 fi 140 } 141 142 # Returns the IP address of an arbitrary Linux webserver pod. 143 function get_linux_webserver_pod_ip { 144 $kubectl get pods -l app="$linux_webserver_pod_label" \ 145 -o jsonpath='{.items[0].status.podIP}' 146 } 147 148 function undeploy_linux_webserver_pod { 149 ${kubectl} delete deployment $linux_webserver_deployment 150 } 151 152 linux_command_deployment=linux-ubuntu 153 linux_command_pod_label=ubuntu 154 linux_command_replicas=1 155 156 function deploy_linux_command_pod { 157 echo "Writing example deployment to $linux_command_deployment.yaml" 158 cat <<EOF > $linux_command_deployment.yaml 159 apiVersion: apps/v1 160 kind: Deployment 161 metadata: 162 name: $linux_command_deployment 163 labels: 164 app: $linux_command_pod_label 165 spec: 166 replicas: $linux_command_replicas 167 selector: 168 matchLabels: 169 app: $linux_command_pod_label 170 template: 171 metadata: 172 labels: 173 app: $linux_command_pod_label 174 spec: 175 containers: 176 - name: ubuntu 177 image: ubuntu 178 command: ["sleep", "123456"] 179 nodeSelector: 180 kubernetes.io/os: linux 181 EOF 182 183 if ! ${kubectl} create -f $linux_command_deployment.yaml; then 184 echo "kubectl create -f $linux_command_deployment.yaml failed" 185 exit 1 186 fi 187 188 timeout=$linux_deployment_timeout 189 while [[ $timeout -gt 0 ]]; do 190 echo "Waiting for $linux_command_replicas Linux $linux_command_pod_label pods to become Ready" 191 statuses=$(${kubectl} get pods -l app="$linux_command_pod_label" \ 192 -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' \ 193 | grep "True" | wc -w) 194 if [[ $statuses -eq $linux_command_replicas ]]; then 195 break 196 else 197 sleep 10 198 (( timeout=timeout-10 )) 199 fi 200 done 201 202 if [[ $timeout -gt 0 ]]; then 203 echo "All $linux_command_pod_label pods became Ready" 204 else 205 echo "ERROR: Not all $linux_command_pod_label pods became Ready" 206 echo "kubectl get pods -l app=$linux_command_pod_label" 207 ${kubectl} get pods -l app="$linux_command_pod_label" 208 cleanup_deployments 209 exit 1 210 fi 211 } 212 213 # Returns the name of an arbitrary Linux command pod. 214 function get_linux_command_pod_name { 215 $kubectl get pods -l app="$linux_command_pod_label" \ 216 -o jsonpath='{.items[0].metadata.name}' 217 } 218 219 # Installs test executables (ping, curl) in the Linux command pod. 220 # NOTE: this assumes that there is only one Linux "command pod". 221 # TODO(pjh): fix this. 222 function prepare_linux_command_pod { 223 local linux_command_pod 224 linux_command_pod="$(get_linux_command_pod_name)" 225 226 echo "Installing test utilities in Linux command pod, may take a minute" 227 $kubectl exec "$linux_command_pod" -- apt-get update > /dev/null 228 $kubectl exec "$linux_command_pod" -- \ 229 apt-get install -y iputils-ping curl > /dev/null 230 } 231 232 function undeploy_linux_command_pod { 233 ${kubectl} delete deployment $linux_command_deployment 234 } 235 236 windows_webserver_deployment=windows-agnhost 237 windows_webserver_pod_label=agnhost 238 # The default port for 'agnhost serve-hostname'. The documentation says that 239 # this can be changed but the --port arg does not seem to work. 240 windows_webserver_port=9376 241 windows_webserver_replicas=1 242 243 function deploy_windows_webserver_pod { 244 echo "Writing example deployment to $windows_webserver_deployment.yaml" 245 cat <<EOF > $windows_webserver_deployment.yaml 246 # A multi-arch Windows container that runs an HTTP server on port 247 # $windows_webserver_port that serves the container's hostname. 248 # curl -s http://<pod_ip>:$windows_webserver_port 249 apiVersion: apps/v1 250 kind: Deployment 251 metadata: 252 name: $windows_webserver_deployment 253 labels: 254 app: $windows_webserver_pod_label 255 spec: 256 replicas: $windows_webserver_replicas 257 selector: 258 matchLabels: 259 app: $windows_webserver_pod_label 260 template: 261 metadata: 262 labels: 263 app: $windows_webserver_pod_label 264 spec: 265 containers: 266 - name: agnhost 267 image: e2eteam/agnhost:2.26 268 args: 269 - serve-hostname 270 nodeSelector: 271 kubernetes.io/os: windows 272 tolerations: 273 - effect: NoSchedule 274 key: node.kubernetes.io/os 275 operator: Equal 276 value: windows 277 EOF 278 279 if ! ${kubectl} create -f $windows_webserver_deployment.yaml; then 280 echo "kubectl create -f $windows_webserver_deployment.yaml failed" 281 exit 1 282 fi 283 284 timeout=$windows_deployment_timeout 285 while [[ $timeout -gt 0 ]]; do 286 echo "Waiting for $windows_webserver_replicas Windows $windows_webserver_pod_label pods to become Ready" 287 statuses=$(${kubectl} get pods -l app=$windows_webserver_pod_label \ 288 -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' \ 289 | grep "True" | wc -w) 290 if [[ $statuses -eq $windows_webserver_replicas ]]; then 291 break 292 else 293 sleep 10 294 (( timeout=timeout-10 )) 295 fi 296 done 297 298 if [[ $timeout -gt 0 ]]; then 299 echo "All $windows_webserver_pod_label pods became Ready" 300 else 301 echo "ERROR: Not all $windows_webserver_pod_label pods became Ready" 302 echo "kubectl get pods -l app=$windows_webserver_pod_label" 303 ${kubectl} get pods -l app=$windows_webserver_pod_label 304 cleanup_deployments 305 exit 1 306 fi 307 } 308 309 function get_windows_webserver_pod_ip { 310 ${kubectl} get pods -l app="$windows_webserver_pod_label" \ 311 -o jsonpath='{.items[0].status.podIP}' 312 } 313 314 function undeploy_windows_webserver_pod { 315 ${kubectl} delete deployment "$windows_webserver_deployment" 316 } 317 318 windows_command_deployment=windows-powershell 319 windows_command_pod_label=powershell 320 windows_command_replicas=1 321 322 # Deploys a multi-arch Windows pod capable of running PowerShell. 323 function deploy_windows_command_pod { 324 echo "Writing example deployment to $windows_command_deployment.yaml" 325 cat <<EOF > $windows_command_deployment.yaml 326 apiVersion: apps/v1 327 kind: Deployment 328 metadata: 329 name: $windows_command_deployment 330 labels: 331 app: $windows_command_pod_label 332 spec: 333 replicas: $windows_command_replicas 334 selector: 335 matchLabels: 336 app: $windows_command_pod_label 337 template: 338 metadata: 339 labels: 340 app: $windows_command_pod_label 341 spec: 342 containers: 343 - name: pause-win 344 image: registry.k8s.io/pause:3.9 345 nodeSelector: 346 kubernetes.io/os: windows 347 tolerations: 348 - effect: NoSchedule 349 key: node.kubernetes.io/os 350 operator: Equal 351 value: windows 352 EOF 353 354 if ! ${kubectl} create -f $windows_command_deployment.yaml; then 355 echo "kubectl create -f $windows_command_deployment.yaml failed" 356 exit 1 357 fi 358 359 timeout=$windows_deployment_timeout 360 while [[ $timeout -gt 0 ]]; do 361 echo "Waiting for $windows_command_replicas Windows $windows_command_pod_label pods to become Ready" 362 statuses=$(${kubectl} get pods -l app=$windows_command_pod_label \ 363 -o jsonpath='{.items[*].status.conditions[?(@.type=="Ready")].status}' \ 364 | grep "True" | wc -w) 365 if [[ $statuses -eq $windows_command_replicas ]]; then 366 break 367 else 368 sleep 10 369 (( timeout=timeout-10 )) 370 fi 371 done 372 373 if [[ $timeout -gt 0 ]]; then 374 echo "All $windows_command_pod_label pods became Ready" 375 else 376 echo "ERROR: Not all $windows_command_pod_label pods became Ready" 377 echo "kubectl get pods -l app=$windows_command_pod_label" 378 ${kubectl} get pods -l app=$windows_command_pod_label 379 cleanup_deployments 380 exit 1 381 fi 382 } 383 384 function get_windows_command_pod_name { 385 $kubectl get pods -l app="$windows_command_pod_label" \ 386 -o jsonpath='{.items[0].metadata.name}' 387 } 388 389 function undeploy_windows_command_pod { 390 ${kubectl} delete deployment "$windows_command_deployment" 391 } 392 393 function test_linux_node_to_linux_pod { 394 echo "TODO: ${FUNCNAME[0]}" 395 } 396 397 function test_linux_node_to_windows_pod { 398 echo "TODO: ${FUNCNAME[0]}" 399 } 400 401 function test_linux_pod_to_linux_pod { 402 echo "TEST: ${FUNCNAME[0]}" 403 local linux_command_pod 404 linux_command_pod="$(get_linux_command_pod_name)" 405 local linux_webserver_pod_ip 406 linux_webserver_pod_ip="$(get_linux_webserver_pod_ip)" 407 408 if ! $kubectl exec "$linux_command_pod" -- curl -s -m 20 \ 409 "http://$linux_webserver_pod_ip" &> $output_file; then 410 cleanup_deployments 411 echo "Failing output: $(cat $output_file)" 412 echo "FAILED: ${FUNCNAME[0]}" 413 exit 1 414 fi 415 } 416 417 # TODO(pjh): this test flakily fails on brand-new clusters, not sure why. 418 # % Total % Received % Xferd Average Speed Time Time Time Current 419 # Dload Upload Total Spent Left Speed 420 # 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 421 # curl: (6) Could not resolve host: 422 # command terminated with exit code 6 423 function test_linux_pod_to_windows_pod { 424 echo "TEST: ${FUNCNAME[0]}" 425 local linux_command_pod 426 linux_command_pod="$(get_linux_command_pod_name)" 427 local windows_webserver_pod_ip 428 windows_webserver_pod_ip="$(get_windows_webserver_pod_ip)" 429 430 if ! $kubectl exec "$linux_command_pod" -- curl -s -m 20 \ 431 "http://$windows_webserver_pod_ip:$windows_webserver_port" &> $output_file; then 432 cleanup_deployments 433 echo "Failing output: $(cat $output_file)" 434 echo "FAILED: ${FUNCNAME[0]}" 435 echo "This test seems to be flaky. TODO(pjh): investigate." 436 exit 1 437 fi 438 } 439 440 function test_linux_pod_to_k8s_service { 441 echo "TEST: ${FUNCNAME[0]}" 442 local linux_command_pod 443 linux_command_pod="$(get_linux_command_pod_name)" 444 local service="metrics-server" 445 local service_ip 446 service_ip=$($kubectl get service --namespace kube-system $service \ 447 -o jsonpath='{.spec.clusterIP}') 448 local service_port 449 service_port=$($kubectl get service --namespace kube-system $service \ 450 -o jsonpath='{.spec.ports[?(@.protocol=="TCP")].port}') 451 echo "curl-ing $service address from Linux pod: $service_ip:$service_port" 452 453 # curl-ing the metrics-server service downloads 14 bytes of unprintable binary 454 # data and sets a return code of success (0). 455 if ! $kubectl exec "$linux_command_pod" -- \ 456 curl -s -m 20 --insecure "https://$service_ip:$service_port" &> $output_file; then 457 cleanup_deployments 458 echo "Failing output: $(cat $output_file)" 459 echo "FAILED: ${FUNCNAME[0]}" 460 exit 1 461 fi 462 } 463 464 function test_windows_node_to_linux_pod { 465 echo "TODO: ${FUNCNAME[0]}" 466 } 467 468 function test_windows_node_to_windows_pod { 469 echo "TODO: ${FUNCNAME[0]}" 470 } 471 472 # TODO(pjh): this test failed for me once with 473 # error: unable to upgrade connection: container not found ("nettest") 474 # Maybe the container crashed for some reason? Investigate if it happens more. 475 # 476 # TODO(pjh): another one-time failure: 477 # error: unable to upgrade connection: Authorization error 478 # (user=kube-apiserver, verb=create, resource=nodes, subresource=proxy) 479 function test_windows_pod_to_linux_pod { 480 echo "TEST: ${FUNCNAME[0]}" 481 local windows_command_pod 482 windows_command_pod="$(get_windows_command_pod_name)" 483 local linux_webserver_pod_ip 484 linux_webserver_pod_ip="$(get_linux_webserver_pod_ip)" 485 486 if ! $kubectl exec "$windows_command_pod" -- powershell.exe \ 487 "curl -UseBasicParsing http://$linux_webserver_pod_ip" > \ 488 $output_file; then 489 cleanup_deployments 490 echo "Failing output: $(cat $output_file)" 491 echo "FAILED: ${FUNCNAME[0]}" 492 exit 1 493 fi 494 } 495 496 function test_windows_pod_to_windows_pod { 497 echo "TEST: ${FUNCNAME[0]}" 498 local windows_command_pod 499 windows_command_pod="$(get_windows_command_pod_name)" 500 local windows_webserver_pod_ip 501 windows_webserver_pod_ip="$(get_windows_webserver_pod_ip)" 502 503 if ! $kubectl exec "$windows_command_pod" -- powershell.exe \ 504 "curl -UseBasicParsing http://$windows_webserver_pod_ip:$windows_webserver_port" \ 505 > $output_file; then 506 cleanup_deployments 507 echo "Failing output: $(cat $output_file)" 508 echo "FAILED: ${FUNCNAME[0]}" 509 exit 1 510 fi 511 } 512 513 function test_windows_pod_to_internet { 514 echo "TEST: ${FUNCNAME[0]}" 515 local windows_command_pod 516 windows_command_pod="$(get_windows_command_pod_name)" 517 # A stable (hopefully) HTTP server provided by Cloudflare. If this ever stops 518 # working, we can request from 8.8.8.8 (Google DNS) using https instead. 519 local internet_ip="1.1.1.1" 520 521 if ! $kubectl exec "$windows_command_pod" -- powershell.exe \ 522 "curl -UseBasicParsing http://$internet_ip" > $output_file; then 523 cleanup_deployments 524 echo "Failing output: $(cat $output_file)" 525 echo "FAILED: ${FUNCNAME[0]}" 526 exit 1 527 fi 528 } 529 530 function test_windows_pod_to_k8s_service { 531 echo "TEST: ${FUNCNAME[0]}" 532 local windows_command_pod 533 windows_command_pod="$(get_windows_command_pod_name)" 534 local service="metrics-server" 535 local service_ip 536 service_ip=$($kubectl get service --namespace kube-system $service \ 537 -o jsonpath='{.spec.clusterIP}') 538 local service_port 539 service_port=$($kubectl get service --namespace kube-system $service \ 540 -o jsonpath='{.spec.ports[?(@.protocol=="TCP")].port}') 541 local service_address="$service_ip:$service_port" 542 543 echo "curl-ing $service address from Windows pod: $service_address" 544 # curl-ing the metrics-server service results in a ServerProtocolViolation 545 # ("The server committed a protocol violation. Section=ResponseStatusLine") 546 # exception. Since we don't care about what the metrics-server actually gives 547 # back to us, just that we can reach it, we check that we get the expected 548 # exception code and not some other exception code. 549 # TODO: it might be less fragile to check that we don't get the "Unable to 550 # connect to the remote server" exception code (2) instead of specifically 551 # expecting the protocol-violation exception code (11). 552 if ! $kubectl exec "$windows_command_pod" -- powershell.exe \ 553 "\$result = try { \` 554 curl -UseBasicParsing http://$service_address -ErrorAction Stop \` 555 } catch [System.Net.WebException] { \` 556 \$_ \` 557 }; \` 558 if ([int]\$result.Exception.Status -eq 11) { \` 559 Write-Host \"curl $service_address got expected exception\" 560 exit 0 \` 561 } else { \` 562 Write-Host \"curl $service_address got unexpected result/exception: \$result\" 563 exit 1 \` 564 }" > $output_file; then 565 cleanup_deployments 566 echo "Failing output: $(cat $output_file)" 567 echo "FAILED: ${FUNCNAME[0]}" 568 exit 1 569 fi 570 } 571 572 function test_kube_dns_in_windows_pod { 573 echo "TEST: ${FUNCNAME[0]}" 574 local windows_command_pod 575 windows_command_pod="$(get_windows_command_pod_name)" 576 local service="kube-dns" 577 local service_ip 578 service_ip=$($kubectl get service --namespace kube-system $service \ 579 -o jsonpath='{.spec.clusterIP}') 580 581 if ! $kubectl exec "$windows_command_pod" -- powershell.exe \ 582 "Resolve-DnsName www.bing.com -server $service_ip" > $output_file; then 583 cleanup_deployments 584 echo "Failing output: $(cat $output_file)" 585 echo "FAILED: ${FUNCNAME[0]}" 586 exit 1 587 fi 588 } 589 590 function test_dns_just_works_in_windows_pod { 591 echo "TEST: ${FUNCNAME[0]}" 592 local windows_command_pod 593 windows_command_pod="$(get_windows_command_pod_name)" 594 595 if ! $kubectl exec "$windows_command_pod" -- powershell.exe \ 596 "curl -UseBasicParsing http://www.bing.com" > $output_file; then 597 cleanup_deployments 598 echo "Failing output: $(cat $output_file)" 599 echo "FAILED: ${FUNCNAME[0]}" 600 exit 1 601 fi 602 } 603 604 function cleanup_deployments { 605 undeploy_linux_webserver_pod 606 undeploy_linux_command_pod 607 undeploy_windows_webserver_pod 608 undeploy_windows_command_pod 609 } 610 611 check_windows_nodes_are_ready 612 untaint_windows_nodes 613 check_no_system_pods_on_windows_nodes 614 615 deploy_linux_webserver_pod 616 deploy_linux_command_pod 617 deploy_windows_webserver_pod 618 deploy_windows_command_pod 619 prepare_linux_command_pod 620 echo "" 621 622 test_linux_node_to_linux_pod 623 test_linux_node_to_windows_pod 624 test_linux_pod_to_linux_pod 625 test_linux_pod_to_windows_pod 626 test_linux_pod_to_k8s_service 627 628 # Note: test_windows_node_to_k8s_service is not supported at this time. 629 # https://docs.microsoft.com/en-us/virtualization/windowscontainers/kubernetes/common-problems#my-windows-node-cannot-access-my-services-using-the-service-ip 630 test_windows_node_to_linux_pod 631 test_windows_node_to_windows_pod 632 test_windows_pod_to_linux_pod 633 test_windows_pod_to_windows_pod 634 test_windows_pod_to_internet 635 test_windows_pod_to_k8s_service 636 test_kube_dns_in_windows_pod 637 test_dns_just_works_in_windows_pod 638 echo "" 639 640 cleanup_deployments 641 echo "All tests passed!" 642 exit 0