github.com/jenkins-x/test-infra@v0.0.7/scenarios/kubernetes_e2e_test.py (about) 1 #!/usr/bin/env python 2 3 # Copyright 2017 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 # Need to figure out why this only fails on travis 18 # pylint: disable=too-few-public-methods 19 20 """Test for kubernetes_e2e.py""" 21 22 import os 23 import shutil 24 import string 25 import tempfile 26 import urllib2 27 import unittest 28 import time 29 30 import kubernetes_e2e 31 32 FAKE_WORKSPACE_STATUS = 'STABLE_BUILD_GIT_COMMIT 599539dc0b99976fda0f326f4ce47e93ec07217c\n' \ 33 'STABLE_BUILD_SCM_STATUS clean\n' \ 34 'STABLE_BUILD_SCM_REVISION v1.7.0-alpha.0.1320+599539dc0b9997\n' \ 35 'STABLE_BUILD_MAJOR_VERSION 1\n' \ 36 'STABLE_BUILD_MINOR_VERSION 7+\n' \ 37 'STABLE_gitCommit 599539dc0b99976fda0f326f4ce47e93ec07217c\n' \ 38 'STABLE_gitTreeState clean\n' \ 39 'STABLE_gitVersion v1.7.0-alpha.0.1320+599539dc0b9997\n' \ 40 'STABLE_gitMajor 1\n' \ 41 'STABLE_gitMinor 7+\n' 42 43 FAKE_WORKSPACE_STATUS_V1_6 = 'STABLE_BUILD_GIT_COMMIT 84febd4537dd190518657405b7bdb921dfbe0387\n' \ 44 'STABLE_BUILD_SCM_STATUS clean\n' \ 45 'STABLE_BUILD_SCM_REVISION v1.6.4-beta.0.18+84febd4537dd19\n' \ 46 'STABLE_BUILD_MAJOR_VERSION 1\n' \ 47 'STABLE_BUILD_MINOR_VERSION 6+\n' \ 48 'STABLE_gitCommit 84febd4537dd190518657405b7bdb921dfbe0387\n' \ 49 'STABLE_gitTreeState clean\n' \ 50 'STABLE_gitVersion v1.6.4-beta.0.18+84febd4537dd19\n' \ 51 'STABLE_gitMajor 1\n' \ 52 'STABLE_gitMinor 6+\n' 53 54 FAKE_DESCRIBE_FROM_FAMILY_RESPONSE = """ 55 archiveSizeBytes: '1581831882' 56 creationTimestamp: '2017-06-16T10:37:57.681-07:00' 57 description: 'Google, Container-Optimized OS, 59-9460.64.0 stable, Kernel: ChromiumOS-4.4.52 58 Kubernetes: 1.6.4 Docker: 1.11.2' 59 diskSizeGb: '10' 60 family: cos-stable 61 id: '2388425242502080922' 62 kind: compute#image 63 labelFingerprint: 42WmSpB8rSM= 64 licenses: 65 - https://www.googleapis.com/compute/v1/projects/cos-cloud/global/licenses/cos 66 name: cos-stable-59-9460-64-0 67 rawDisk: 68 containerType: TAR 69 source: '' 70 selfLink: https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-59-9460-64-0 71 sourceType: RAW 72 status: READY 73 """ 74 75 def fake_pass(*_unused, **_unused2): 76 """Do nothing.""" 77 pass 78 79 def fake_bomb(*a, **kw): 80 """Always raise.""" 81 raise AssertionError('Should not happen', a, kw) 82 83 def raise_urllib2_error(*_unused, **_unused2): 84 """Always raise a urllib2.URLError""" 85 raise urllib2.URLError("test failure") 86 87 def always_kubernetes(*_unused, **_unused2): 88 """Always return 'kubernetes'""" 89 return 'kubernetes' 90 91 class Stub(object): 92 """Replace thing.param with replacement until exiting with.""" 93 def __init__(self, thing, param, replacement): 94 self.thing = thing 95 self.param = param 96 self.replacement = replacement 97 self.old = getattr(thing, param) 98 setattr(thing, param, self.replacement) 99 100 def __enter__(self, *a, **kw): 101 return self.replacement 102 103 def __exit__(self, *a, **kw): 104 setattr(self.thing, self.param, self.old) 105 106 107 class ClusterNameTest(unittest.TestCase): 108 def test_name_filled(self): 109 """Return the cluster name if set.""" 110 name = 'foo' 111 build = '1984' 112 os.environ['BUILD_ID'] = build 113 actual = kubernetes_e2e.cluster_name(name) 114 self.assertTrue(actual) 115 self.assertIn(name, actual) 116 self.assertNotIn(build, actual) 117 118 def test_name_empty_short_build(self): 119 """Return the build number if name is empty.""" 120 name = '' 121 build = '1984' 122 os.environ['BUILD_ID'] = build 123 actual = kubernetes_e2e.cluster_name(name) 124 self.assertTrue(actual) 125 self.assertIn(build, actual) 126 127 def test_name_empty_long_build(self): 128 """Return a short hash of a long build number if name is empty.""" 129 name = '' 130 build = '0' * 63 131 os.environ['BUILD_ID'] = build 132 actual = kubernetes_e2e.cluster_name(name) 133 self.assertTrue(actual) 134 self.assertNotIn(build, actual) 135 if len(actual) > 32: # Some firewall names consume half the quota 136 self.fail('Name should be short: %s' % actual) 137 138 def test_name_presubmit(self): 139 """Return the build number if name is empty.""" 140 name = '' 141 build = '1984' 142 pr = '12345' 143 os.environ['BUILD_ID'] = build 144 os.environ['JOB_TYPE'] = 'presubmit' 145 os.environ['PULL_NUMBER'] = pr 146 actual = kubernetes_e2e.cluster_name(name, False) 147 self.assertTrue(actual) 148 self.assertIn(build, actual) 149 self.assertNotIn(pr, actual) 150 151 actual = kubernetes_e2e.cluster_name(name, True) 152 self.assertTrue(actual) 153 self.assertIn(pr, actual) 154 self.assertNotIn(build, actual) 155 156 157 class ScenarioTest(unittest.TestCase): # pylint: disable=too-many-public-methods 158 """Test for e2e scenario.""" 159 callstack = [] 160 envs = {} 161 162 def setUp(self): 163 self.boiler = [ 164 Stub(kubernetes_e2e, 'check', self.fake_check), 165 Stub(shutil, 'copy', fake_pass), 166 ] 167 168 def tearDown(self): 169 for stub in self.boiler: 170 with stub: # Leaving with restores things 171 pass 172 self.callstack[:] = [] 173 self.envs.clear() 174 175 def fake_check(self, *cmd): 176 """Log the command.""" 177 self.callstack.append(string.join(cmd)) 178 179 def fake_check_env(self, env, *cmd): 180 """Log the command with a specific env.""" 181 self.envs.update(env) 182 self.callstack.append(string.join(cmd)) 183 184 def fake_output_work_status(self, *cmd): 185 """fake a workstatus blob.""" 186 self.callstack.append(string.join(cmd)) 187 return FAKE_WORKSPACE_STATUS 188 189 def fake_output_work_status_v1_6(self, *cmd): 190 """fake a workstatus blob for v1.6.""" 191 self.callstack.append(string.join(cmd)) 192 return FAKE_WORKSPACE_STATUS_V1_6 193 194 def fake_output_get_latest_image(self, *cmd): 195 """fake a `gcloud compute images describe-from-family` response.""" 196 self.callstack.append(string.join(cmd)) 197 return FAKE_DESCRIBE_FROM_FAMILY_RESPONSE 198 199 def test_local(self): 200 """Make sure local mode is fine overall.""" 201 args = kubernetes_e2e.parse_args() 202 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 203 kubernetes_e2e.main(args) 204 205 self.assertNotEqual(self.envs, {}) 206 for call in self.callstack: 207 self.assertFalse(call.startswith('docker')) 208 209 def test_check_leaks(self): 210 """Ensure --check-leaked-resources=true sends flag to kubetest.""" 211 args = kubernetes_e2e.parse_args(['--check-leaked-resources=true']) 212 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 213 kubernetes_e2e.main(args) 214 self.assertIn('--check-leaked-resources=true', self.callstack[-1]) 215 216 def test_check_leaks_false(self): 217 """Ensure --check-leaked-resources=true sends flag to kubetest.""" 218 args = kubernetes_e2e.parse_args(['--check-leaked-resources=false']) 219 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 220 kubernetes_e2e.main(args) 221 self.assertIn('--check-leaked-resources=false', self.callstack[-1]) 222 223 def test_check_leaks_default(self): 224 """Ensure --check-leaked-resources=true sends flag to kubetest.""" 225 args = kubernetes_e2e.parse_args(['--check-leaked-resources']) 226 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 227 kubernetes_e2e.main(args) 228 self.assertIn('--check-leaked-resources', self.callstack[-1]) 229 230 def test_check_leaks_unset(self): 231 """Ensure --check-leaked-resources=true sends flag to kubetest.""" 232 args = kubernetes_e2e.parse_args(['--mode=local']) 233 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 234 kubernetes_e2e.main(args) 235 self.assertNotIn('--check-leaked-resources', self.callstack[-1]) 236 237 def test_migrated_kubetest_args(self): 238 migrated = [ 239 '--stage-suffix=panda', 240 '--random-flag', 'random-value', 241 '--multiple-federations', 242 'arg1', 'arg2', 243 '--federation', 244 '--kubemark', 245 '--extract=this', 246 '--extract=that', 247 '--save=somewhere', 248 '--skew', 249 '--publish=location', 250 '--timeout=42m', 251 '--upgrade_args=ginkgo', 252 '--check-leaked-resources=true', 253 '--charts', 254 ] 255 explicit_passthrough_args = [ 256 '--deployment=yay', 257 '--provider=gce', 258 ] 259 args = kubernetes_e2e.parse_args(migrated 260 + explicit_passthrough_args 261 + ['--test=false']) 262 self.assertEquals(migrated, args.kubetest_args) 263 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 264 kubernetes_e2e.main(args) 265 lastcall = self.callstack[-1] 266 for arg in migrated: 267 self.assertIn(arg, lastcall) 268 for arg in explicit_passthrough_args: 269 self.assertIn(arg, lastcall) 270 271 def test_updown_default(self): 272 args = kubernetes_e2e.parse_args([]) 273 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 274 kubernetes_e2e.main(args) 275 lastcall = self.callstack[-1] 276 self.assertIn('--up', lastcall) 277 self.assertIn('--down', lastcall) 278 279 def test_updown_set(self): 280 args = kubernetes_e2e.parse_args(['--up=false', '--down=true']) 281 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 282 kubernetes_e2e.main(args) 283 lastcall = self.callstack[-1] 284 self.assertNotIn('--up', lastcall) 285 self.assertIn('--down', lastcall) 286 287 288 def test_kubeadm_ci(self): 289 """Make sure kubeadm ci mode is fine overall.""" 290 args = kubernetes_e2e.parse_args(['--kubeadm=ci']) 291 self.assertEqual(args.kubeadm, 'ci') 292 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 293 with Stub(kubernetes_e2e, 'check_output', self.fake_output_work_status): 294 kubernetes_e2e.main(args) 295 296 self.assertNotIn('E2E_OPT', self.envs) 297 version = 'gs://kubernetes-release-dev/ci/v1.7.0-alpha.0.1320+599539dc0b9997-bazel/bin/linux/amd64/' # pylint: disable=line-too-long 298 self.assertIn('--kubernetes-anywhere-kubeadm-version=%s' % version, self.callstack[-1]) 299 called = False 300 for call in self.callstack: 301 self.assertFalse(call.startswith('docker')) 302 if call == 'hack/print-workspace-status.sh': 303 called = True 304 self.assertTrue(called) 305 306 def test_local_env(self): 307 """ 308 Ensure that host variables (such as GOPATH) are included, 309 and added envs/env files overwrite os environment. 310 """ 311 mode = kubernetes_e2e.LocalMode('/orig-workspace', '/random-artifacts') 312 mode.add_environment(*( 313 'FOO=BAR', 'GOPATH=/go/path', 'WORKSPACE=/new/workspace')) 314 mode.add_os_environment(*('USER=jenkins', 'FOO=BAZ', 'GOOS=linux')) 315 with tempfile.NamedTemporaryFile() as temp: 316 temp.write('USER=prow') 317 temp.flush() 318 mode.add_file(temp.name) 319 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 320 mode.start([]) 321 self.assertIn(('FOO', 'BAR'), self.envs.viewitems()) 322 self.assertIn(('WORKSPACE', '/new/workspace'), self.envs.viewitems()) 323 self.assertIn(('GOPATH', '/go/path'), self.envs.viewitems()) 324 self.assertIn(('USER', 'prow'), self.envs.viewitems()) 325 self.assertIn(('GOOS', 'linux'), self.envs.viewitems()) 326 self.assertNotIn(('USER', 'jenkins'), self.envs.viewitems()) 327 self.assertNotIn(('FOO', 'BAZ'), self.envs.viewitems()) 328 329 def test_kubeadm_periodic(self): 330 """Make sure kubeadm periodic mode is fine overall.""" 331 args = kubernetes_e2e.parse_args(['--kubeadm=periodic']) 332 self.assertEqual(args.kubeadm, 'periodic') 333 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 334 with Stub(kubernetes_e2e, 'check_output', self.fake_output_work_status): 335 kubernetes_e2e.main(args) 336 337 self.assertNotIn('E2E_OPT', self.envs) 338 version = 'gs://kubernetes-release-dev/ci/v1.7.0-alpha.0.1320+599539dc0b9997-bazel/bin/linux/amd64/' # pylint: disable=line-too-long 339 self.assertIn('--kubernetes-anywhere-kubeadm-version=%s' % version, self.callstack[-1]) 340 called = False 341 for call in self.callstack: 342 self.assertFalse(call.startswith('docker')) 343 if call == 'hack/print-workspace-status.sh': 344 called = True 345 self.assertTrue(called) 346 347 def test_kubeadm_pull(self): 348 """Make sure kubeadm pull mode is fine overall.""" 349 args = kubernetes_e2e.parse_args([ 350 '--kubeadm=pull', 351 '--use-shared-build=bazel' 352 ]) 353 self.assertEqual(args.kubeadm, 'pull') 354 self.assertEqual(args.use_shared_build, 'bazel') 355 356 gcs_bucket = "gs://kubernetes-release-dev/bazel/v1.8.0-beta.1.132+599539dc0b9997" 357 358 def fake_gcs_path(path): 359 bazel_default = os.path.join( 360 'gs://kubernetes-jenkins/shared-results', 'bazel-build-location.txt') 361 self.assertEqual(path, bazel_default) 362 return gcs_bucket 363 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 364 with Stub(kubernetes_e2e, 'read_gcs_path', fake_gcs_path): 365 kubernetes_e2e.main(args) 366 367 self.assertNotIn('E2E_OPT', self.envs) 368 version = '%s/bin/linux/amd64/' % gcs_bucket 369 self.assertIn('--kubernetes-anywhere-kubeadm-version=%s' % version, self.callstack[-1]) 370 371 def test_kubeadm_invalid(self): 372 """Make sure kubeadm invalid mode exits unsuccessfully.""" 373 with self.assertRaises(SystemExit) as sysexit: 374 kubernetes_e2e.parse_args(['--mode=local', '--kubeadm=deploy']) 375 376 self.assertEqual(sysexit.exception.code, 2) 377 378 def test_parse_args_order_agnostic(self): 379 args = kubernetes_e2e.parse_args([ 380 '--some-kubetest-arg=foo', 381 '--cluster=test']) 382 self.assertEqual(args.kubetest_args, ['--some-kubetest-arg=foo']) 383 self.assertEqual(args.cluster, 'test') 384 385 def test_gcp_network(self): 386 args = kubernetes_e2e.parse_args(['--mode=local', '--cluster=test']) 387 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 388 kubernetes_e2e.main(args) 389 lastcall = self.callstack[-1] 390 self.assertIn('--gcp-network=test', lastcall) 391 392 def test_env_local(self): 393 env = 'FOO' 394 value = 'BLAT' 395 args = kubernetes_e2e.parse_args([ 396 '--mode=local', 397 '--env={env}={value}'.format(env=env, value=value), 398 ]) 399 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 400 kubernetes_e2e.main(args) 401 self.assertIn(env, self.envs) 402 self.assertEqual(self.envs[env], value) 403 404 def test_aws(self): 405 temp = tempfile.NamedTemporaryFile() 406 args = kubernetes_e2e.parse_args([ 407 '--aws', 408 '--cluster=foo', 409 '--aws-cluster-domain=test-aws.k8s.io', 410 '--aws-ssh=%s' % temp.name, 411 '--aws-pub=%s' % temp.name, 412 '--aws-cred=%s' % temp.name, 413 ]) 414 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 415 kubernetes_e2e.main(args) 416 417 lastcall = self.callstack[-1] 418 self.assertIn('kops-e2e-runner.sh', lastcall) 419 self.assertIn('--kops-cluster=foo.test-aws.k8s.io', lastcall) 420 self.assertIn('--kops-zones', lastcall) 421 self.assertIn('--kops-state=s3://k8s-kops-prow/', lastcall) 422 self.assertIn('--kops-nodes=4', lastcall) 423 self.assertIn('--kops-ssh-key', lastcall) 424 425 self.assertNotIn('kubetest', lastcall) 426 self.assertIn('kops-e2e-runner.sh', lastcall) 427 428 self.assertEqual( 429 self.envs['JENKINS_AWS_SSH_PRIVATE_KEY_FILE'], temp.name) 430 self.assertEqual( 431 self.envs['JENKINS_AWS_SSH_PUBLIC_KEY_FILE'], temp.name) 432 self.assertEqual( 433 self.envs['JENKINS_AWS_CREDENTIALS_FILE'], temp.name) 434 435 def test_kops_aws(self): 436 temp = tempfile.NamedTemporaryFile() 437 args = kubernetes_e2e.parse_args([ 438 '--provider=aws', 439 '--deployment=kops', 440 '--cluster=foo.example.com', 441 '--aws-ssh=%s' % temp.name, 442 '--aws-pub=%s' % temp.name, 443 '--aws-cred=%s' % temp.name, 444 ]) 445 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 446 kubernetes_e2e.main(args) 447 448 lastcall = self.callstack[-1] 449 self.assertIn('kubetest', lastcall) 450 self.assertIn('--provider=aws', lastcall) 451 self.assertIn('--deployment=kops', lastcall) 452 self.assertIn('--kops-cluster=foo.example.com', lastcall) 453 self.assertIn('--kops-zones', lastcall) 454 self.assertIn('--kops-state=s3://k8s-kops-prow/', lastcall) 455 self.assertIn('--kops-nodes=4', lastcall) 456 self.assertIn('--kops-ssh-key', lastcall) 457 self.assertIn('kubetest', lastcall) 458 self.assertNotIn('kops-e2e-runner.sh', lastcall) 459 460 def test_kops_gce(self): 461 temp = tempfile.NamedTemporaryFile() 462 args = kubernetes_e2e.parse_args([ 463 '--provider=gce', 464 '--deployment=kops', 465 '--cluster=foo.example.com', 466 '--gce-ssh=%s' % temp.name, 467 '--gce-pub=%s' % temp.name, 468 ]) 469 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 470 kubernetes_e2e.main(args) 471 472 lastcall = self.callstack[-1] 473 self.assertIn('kubetest', lastcall) 474 self.assertIn('--provider=gce', lastcall) 475 self.assertIn('--deployment=kops', lastcall) 476 self.assertIn('--kops-cluster=foo.example.com', lastcall) 477 self.assertIn('--kops-zones', lastcall) 478 self.assertIn('--kops-state=gs://k8s-kops-gce/', lastcall) 479 self.assertIn('--kops-nodes=4', lastcall) 480 self.assertIn('--kops-ssh-key', lastcall) 481 482 def test_use_shared_build(self): 483 # normal path 484 args = kubernetes_e2e.parse_args([ 485 '--use-shared-build=bazel' 486 ]) 487 def expect_bazel_gcs(path): 488 bazel_default = os.path.join( 489 'gs://kubernetes-jenkins/shared-results', 'bazel-build-location.txt') 490 self.assertEqual(path, bazel_default) 491 return always_kubernetes() 492 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 493 with Stub(kubernetes_e2e, 'read_gcs_path', expect_bazel_gcs): 494 with Stub(time, 'sleep', fake_pass): 495 kubernetes_e2e.main(args) 496 lastcall = self.callstack[-1] 497 self.assertIn('--extract=kubernetes', lastcall) 498 # normal path, not bazel 499 args = kubernetes_e2e.parse_args([ 500 '--use-shared-build' 501 ]) 502 def expect_normal_gcs(path): 503 bazel_default = os.path.join( 504 'gs://kubernetes-jenkins/shared-results', 'build-location.txt') 505 self.assertEqual(path, bazel_default) 506 return always_kubernetes() 507 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 508 with Stub(kubernetes_e2e, 'read_gcs_path', expect_normal_gcs): 509 kubernetes_e2e.main(args) 510 lastcall = self.callstack[-1] 511 self.assertIn('--extract=kubernetes', lastcall) 512 # test failure to read shared path from GCS 513 with Stub(kubernetes_e2e, 'check_env', self.fake_check_env): 514 with Stub(kubernetes_e2e, 'read_gcs_path', raise_urllib2_error): 515 with Stub(os, 'getcwd', always_kubernetes): 516 with Stub(time, 'sleep', fake_pass): 517 try: 518 kubernetes_e2e.main(args) 519 except RuntimeError as err: 520 if not err.message.startswith('Failed to get shared build location'): 521 raise err 522 523 if __name__ == '__main__': 524 unittest.main()