github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/tests/test_deploy_stack.py (about) 1 from argparse import ( 2 Namespace, 3 ) 4 from contextlib import contextmanager 5 from datetime import ( 6 datetime, 7 timedelta, 8 ) 9 import json 10 import logging 11 import os 12 import subprocess 13 from tempfile import NamedTemporaryFile 14 from unittest import ( 15 TestCase, 16 ) 17 try: 18 from mock import ( 19 call, 20 MagicMock, 21 patch, 22 Mock, 23 ) 24 except ImportError: 25 from unittest.mock import ( 26 call, 27 MagicMock, 28 patch, 29 Mock, 30 ) 31 import yaml 32 33 from deploy_stack import ( 34 archive_logs, 35 assess_juju_relations, 36 assess_juju_run, 37 assess_upgrade, 38 boot_context, 39 BootstrapManager, 40 check_token, 41 copy_remote_logs, 42 CreateController, 43 ExistingController, 44 deploy_dummy_stack, 45 deploy_job, 46 _deploy_job, 47 deploy_job_parse_args, 48 dump_env_logs, 49 dump_juju_timings, 50 _get_clients_to_upgrade, 51 iter_remote_machines, 52 get_remote_machines, 53 make_controller_strategy, 54 PublicController, 55 safe_print_status, 56 update_env, 57 wait_for_state_server_to_shutdown, 58 error_if_unclean, 59 ) 60 from jujupy import ( 61 fake_juju_client, 62 get_cache_path, 63 get_timeout_prefix, 64 JujuData, 65 ModelClient, 66 Machine, 67 ) 68 69 from jujupy.exceptions import ( 70 SoftDeadlineExceeded, 71 ) 72 from jujupy.status import ( 73 Status, 74 ) 75 from jujupy.wait_condition import ( 76 CommandTime, 77 ) 78 from remote import ( 79 _Remote, 80 remote_from_address, 81 SSHRemote, 82 winrm, 83 ) 84 from tests import ( 85 assert_juju_call, 86 FakeHomeTestCase, 87 FakePopen, 88 make_fake_juju_return, 89 observable_temp_file, 90 temp_os_env, 91 use_context, 92 ) 93 from utility import ( 94 LoggedException, 95 temp_dir, 96 ) 97 98 99 class DeployStackTestCase(FakeHomeTestCase): 100 101 log_level = logging.DEBUG 102 103 def test_assess_juju_run(self): 104 env = JujuData('foo', {'type': 'nonlocal'}) 105 client = ModelClient(env, None, None) 106 response_ok = json.dumps( 107 [{"MachineId": "1", "Stdout": "Linux\n"}, 108 {"MachineId": "2", "Stdout": "Linux\n"}]) 109 response_err = json.dumps([ 110 {"MachineId": "1", "Stdout": "Linux\n"}, 111 {"MachineId": "2", 112 "Stdout": "Linux\n", 113 "ReturnCode": 255, 114 "Stderr": "Permission denied (publickey,password)"}]) 115 with patch.object(client, 'get_juju_output', autospec=True, 116 return_value=response_ok) as gjo_mock: 117 responses = assess_juju_run(client) 118 for machine in responses: 119 self.assertFalse(machine.get('ReturnCode', False)) 120 self.assertIn(machine.get('MachineId'), ["1", "2"]) 121 self.assertEqual(len(responses), 2) 122 gjo_mock.assert_called_once_with( 123 'run', '--format', 'json', '--application', 124 'dummy-source,dummy-sink', 'uname') 125 with patch.object(client, 'get_juju_output', autospec=True, 126 return_value=response_err) as gjo_mock: 127 with self.assertRaises(ValueError): 128 responses = assess_juju_run(client) 129 gjo_mock.assert_called_once_with( 130 'run', '--format', 'json', '--application', 131 'dummy-source,dummy-sink', 'uname') 132 133 def test_safe_print_status(self): 134 env = JujuData('foo', {'type': 'nonlocal'}) 135 client = ModelClient(env, None, None) 136 error = subprocess.CalledProcessError(1, 'status', 'status error') 137 with patch.object(client, 'juju', autospec=True, 138 side_effect=[error]) as mock: 139 with patch.object(client, 'iter_model_clients', 140 return_value=[client]) as imc_mock: 141 safe_print_status(client) 142 mock.assert_called_once_with('show-status', ('--format', 'yaml')) 143 imc_mock.assert_called_once_with() 144 145 def test_safe_print_status_ignores_soft_deadline(self): 146 client = fake_juju_client() 147 client._backend._past_deadline = True 148 client.bootstrap() 149 150 def raise_exception(e): 151 raise e 152 153 try: 154 with patch('logging.exception', side_effect=raise_exception): 155 safe_print_status(client) 156 except SoftDeadlineExceeded: 157 self.fail('Raised SoftDeadlineExceeded.') 158 159 def test_update_env(self): 160 env = JujuData('foo', {'type': 'paas'}) 161 update_env( 162 env, 'bar', series='wacky', bootstrap_host='baz', 163 agent_url='url', agent_stream='devel') 164 self.assertEqual('bar', env.environment) 165 self.assertEqual('bar', env.get_option('name')) 166 self.assertEqual('wacky', env.get_option('default-series')) 167 self.assertEqual('baz', env.get_option('bootstrap-host')) 168 self.assertEqual('url', env.get_option('agent-metadata-url')) 169 self.assertEqual('devel', env.get_option('agent-stream')) 170 self.assertNotIn('region', env._config) 171 172 def test_update_env_region(self): 173 env = JujuData('foo', {'type': 'paas'}) 174 update_env(env, 'bar', region='region-foo') 175 self.assertEqual('region-foo', env.get_region()) 176 177 def test_update_env_region_none(self): 178 env = JujuData( 179 'foo', 180 {'type': 'paas', 'region': 'region-foo'}) 181 update_env(env, 'bar', region=None) 182 self.assertEqual('region-foo', env.get_region()) 183 184 def test_dump_juju_timings(self): 185 first_start = datetime(2017, 3, 22, 23, 36, 52, 0) 186 first_end = first_start + timedelta(seconds=2) 187 second_start = datetime(2017, 3, 22, 23, 40, 51, 0) 188 env = JujuData('foo', {'type': 'bar'}) 189 client = ModelClient(env, None, None) 190 client._backend.juju_timings.extend([ 191 CommandTime('command1', ['command1', 'arg1'], start=first_start), 192 CommandTime( 193 'command2', ['command2', 'arg1', 'arg2'], start=second_start) 194 ]) 195 client._backend.juju_timings[0].actual_completion(end=first_end) 196 expected = [ 197 { 198 'command': 'command1', 199 'full_args': ['command1', 'arg1'], 200 'start': first_start, 201 'end': first_end, 202 'total_seconds': 2, 203 }, 204 { 205 'command': 'command2', 206 'full_args': ['command2', 'arg1', 'arg2'], 207 'start': second_start, 208 'end': None, 209 'total_seconds': None, 210 } 211 ] 212 with temp_dir() as fake_dir: 213 dump_juju_timings(client, fake_dir) 214 with open(os.path.join(fake_dir, 215 'juju_command_times.yaml')) as out_file: 216 file_data = yaml.load(out_file) 217 self.assertEqual(file_data, expected) 218 219 def test_check_token(self): 220 env = JujuData('foo', {'type': 'lxd'}) 221 client = ModelClient(env, None, None) 222 status = Status.from_text("""\ 223 applications: 224 dummy-sink: 225 units: 226 dummy-sink/0: 227 workload-status: 228 current: active 229 message: Token is token 230 231 """) 232 remote = SSHRemote(client, 'unit', None, series='bionic') 233 with patch('deploy_stack.remote_from_unit', autospec=True, 234 return_value=remote): 235 with patch.object(remote, 'cat', autospec=True, 236 return_value='token') as rc_mock: 237 with patch.object(client, 'get_status', autospec=True, 238 return_value=status): 239 check_token(client, 'token', timeout=0) 240 rc_mock.assert_called_once_with('/var/run/dummy-sink/token') 241 self.assertTrue(remote.use_juju_ssh) 242 self.assertEqual( 243 ['INFO Waiting for applications to reach ready.', 244 'INFO Retrieving token.', 245 "INFO Token matches expected 'token'"], 246 self.log_stream.getvalue().splitlines()) 247 248 def test_check_token_not_found(self): 249 env = JujuData('foo', {'type': 'lxd'}) 250 client = ModelClient(env, None, None) 251 status = Status.from_text("""\ 252 applications: 253 dummy-sink: 254 units: 255 dummy-sink/0: 256 workload-status: 257 current: active 258 message: Waiting for token 259 260 """) 261 remote = SSHRemote(client, 'unit', None, series='bionic') 262 error = subprocess.CalledProcessError(1, 'ssh', '') 263 with patch('deploy_stack.remote_from_unit', autospec=True, 264 return_value=remote): 265 with patch.object(remote, 'cat', autospec=True, 266 side_effect=error) as rc_mock: 267 with patch.object(remote, 'get_address', 268 autospec=True) as ga_mock: 269 with patch.object(client, 'get_status', autospec=True, 270 return_value=status): 271 with self.assertRaisesRegexp(ValueError, 272 "Token is ''"): 273 check_token(client, 'token', timeout=0) 274 self.assertEqual(2, rc_mock.call_count) 275 ga_mock.assert_called_once_with() 276 self.assertFalse(remote.use_juju_ssh) 277 self.assertEqual( 278 ['INFO Waiting for applications to reach ready.', 279 'INFO Retrieving token.'], 280 self.log_stream.getvalue().splitlines()) 281 282 def test_check_token_not_found_juju_ssh_broken(self): 283 env = JujuData('foo', {'type': 'lxd'}) 284 client = ModelClient(env, None, None) 285 status = Status.from_text("""\ 286 applications: 287 dummy-sink: 288 units: 289 dummy-sink/0: 290 workload-status: 291 current: active 292 message: Token is token 293 294 """) 295 remote = SSHRemote(client, 'unit', None, series='bionic') 296 error = subprocess.CalledProcessError(1, 'ssh', '') 297 with patch('deploy_stack.remote_from_unit', autospec=True, 298 return_value=remote): 299 with patch.object(remote, 'cat', autospec=True, 300 side_effect=[error, 'token']) as rc_mock: 301 with patch.object(remote, 'get_address', 302 autospec=True) as ga_mock: 303 with patch.object(client, 'get_status', autospec=True, 304 return_value=status): 305 with self.assertRaisesRegexp(ValueError, 306 "Token is 'token'"): 307 check_token(client, 'token', timeout=0) 308 self.assertEqual(2, rc_mock.call_count) 309 rc_mock.assert_called_with('/var/run/dummy-sink/token') 310 ga_mock.assert_called_once_with() 311 self.assertFalse(remote.use_juju_ssh) 312 self.assertEqual( 313 ['INFO Waiting for applications to reach ready.', 314 'INFO Retrieving token.', 315 "INFO Token matches expected 'token'", 316 'ERROR juju ssh to unit is broken.'], 317 self.log_stream.getvalue().splitlines()) 318 319 def test_check_token_win_status(self): 320 env = JujuData('foo', {'type': 'azure'}) 321 client = ModelClient(env, None, None) 322 remote = MagicMock(spec=['cat', 'is_windows']) 323 remote.is_windows.return_value = True 324 status = Status.from_text("""\ 325 applications: 326 dummy-sink: 327 units: 328 dummy-sink/0: 329 workload-status: 330 current: active 331 message: Token is token 332 333 """) 334 with patch('deploy_stack.remote_from_unit', autospec=True, 335 return_value=remote): 336 with patch.object(client, 'get_status', autospec=True, 337 return_value=status): 338 check_token(client, 'token', timeout=0) 339 # application-status had the token. 340 self.assertEqual(0, remote.cat.call_count) 341 self.assertEqual( 342 ['INFO Waiting for applications to reach ready.', 343 'INFO Retrieving token.', 344 "INFO Token matches expected 'token'"], 345 self.log_stream.getvalue().splitlines()) 346 347 def test_check_token_win_client_status(self): 348 env = JujuData('foo', {'type': 'ec2'}) 349 client = ModelClient(env, None, None) 350 remote = MagicMock(spec=['cat', 'is_windows']) 351 remote.is_windows.return_value = False 352 status = Status.from_text("""\ 353 applications: 354 dummy-sink: 355 units: 356 dummy-sink/0: 357 workload-status: 358 current: active 359 message: Token is token 360 361 """) 362 with patch('deploy_stack.remote_from_unit', autospec=True, 363 return_value=remote): 364 with patch.object(client, 'get_status', autospec=True, 365 return_value=status): 366 with patch('sys.platform', 'win32'): 367 check_token(client, 'token', timeout=0) 368 # application-status had the token. 369 self.assertEqual(0, remote.cat.call_count) 370 self.assertEqual( 371 ['INFO Waiting for applications to reach ready.', 372 'INFO Retrieving token.', 373 "INFO Token matches expected 'token'"], 374 self.log_stream.getvalue().splitlines()) 375 376 def test_check_token_win_remote(self): 377 env = JujuData('foo', {'type': 'azure'}) 378 client = ModelClient(env, None, None) 379 remote = MagicMock(spec=['cat', 'is_windows']) 380 remote.is_windows.return_value = True 381 remote.cat.return_value = 'token' 382 status = Status.from_text("""\ 383 applications: 384 dummy-sink: 385 units: 386 dummy-sink/0: 387 juju-status: 388 current: active 389 """) 390 with patch('deploy_stack.remote_from_unit', autospec=True, 391 return_value=remote): 392 with patch.object(client, 'get_status', autospec=True, 393 return_value=status): 394 check_token(client, 'token', timeout=0) 395 # application-status did not have the token, winrm did. 396 remote.cat.assert_called_once_with('%ProgramData%\\dummy-sink\\token') 397 self.assertEqual( 398 ['INFO Waiting for applications to reach ready.', 399 'INFO Retrieving token.', 400 "INFO Token matches expected 'token'"], 401 self.log_stream.getvalue().splitlines()) 402 403 def test_check_token_win_remote_failure(self): 404 env = JujuData('foo', {'type': 'azure'}) 405 client = ModelClient(env, None, None) 406 remote = MagicMock(spec=['cat', 'is_windows']) 407 remote.is_windows.return_value = True 408 error = winrm.exceptions.WinRMTransportError('a', 'oops') 409 remote.cat.side_effect = error 410 status = Status.from_text("""\ 411 applications: 412 dummy-sink: 413 units: 414 dummy-sink/0: 415 juju-status: 416 current: active 417 """) 418 with patch('deploy_stack.remote_from_unit', autospec=True, 419 return_value=remote): 420 with patch.object(client, 'get_status', autospec=True, 421 return_value=status): 422 with self.assertRaises(type(error)) as ctx: 423 check_token(client, 'token', timeout=0) 424 self.assertIs(ctx.exception, error) 425 remote.cat.assert_called_once_with('%ProgramData%\\dummy-sink\\token') 426 self.assertEqual( 427 ['INFO Waiting for applications to reach ready.', 428 'INFO Retrieving token.'], 429 self.log_stream.getvalue().splitlines()) 430 431 432 @contextmanager 433 def _fake_winrm_certs(): 434 with NamedTemporaryFile() as cert_file: 435 fake_cert = (cert_file.name, cert_file.name) 436 with patch('utility.get_winrm_certs', return_value=fake_cert): 437 yield 438 439 440 class DumpEnvLogsTestCase(FakeHomeTestCase): 441 442 log_level = logging.DEBUG 443 444 def assert_machines(self, expected, got): 445 self.assertEqual(expected, dict((k, got[k].address) for k in got)) 446 447 with _fake_winrm_certs(): 448 r0 = remote_from_address('10.10.0.1') 449 r1 = remote_from_address('10.10.0.11') 450 r2 = remote_from_address('10.10.0.22', series='win2012hvr2') 451 452 @classmethod 453 def fake_remote_machines(cls): 454 return {'0': cls.r0, '1': cls.r1, '2': cls.r2} 455 456 def test_dump_env_logs_remote(self): 457 with temp_dir() as artifacts_dir: 458 with patch('deploy_stack.get_remote_machines', autospec=True, 459 return_value=self.fake_remote_machines()) as gm_mock: 460 with patch('deploy_stack._can_run_ssh', lambda: True): 461 with patch('deploy_stack.copy_remote_logs', 462 autospec=True) as crl_mock: 463 with patch('deploy_stack.archive_logs', 464 autospec=True) as al_mock: 465 env = JujuData('foo', {'type': 'nonlocal'}) 466 client = ModelClient(env, '1.234-76', None) 467 dump_env_logs(client, '10.10.0.1', artifacts_dir) 468 al_mock.assert_called_once_with(artifacts_dir) 469 self.assertEqual( 470 ['machine-0', 'machine-1', 'machine-2'], 471 sorted(os.listdir(artifacts_dir))) 472 self.assertEqual( 473 (client, {'0': '10.10.0.1'}), gm_mock.call_args[0]) 474 self.assertItemsEqual( 475 [(self.r0, '%s/machine-0' % artifacts_dir), 476 (self.r1, '%s/machine-1' % artifacts_dir), 477 (self.r2, '%s/machine-2' % artifacts_dir)], 478 [cal[0] for cal in crl_mock.call_args_list]) 479 self.assertEqual( 480 ['INFO Retrieving logs for machine-0 using ' + repr(self.r0), 481 'INFO Retrieving logs for machine-1 using ' + repr(self.r1), 482 'INFO Retrieving logs for machine-2 using ' + repr(self.r2)], 483 self.log_stream.getvalue().splitlines()) 484 485 def test_dump_env_logs_remote_no_ssh(self): 486 with temp_dir() as artifacts_dir: 487 with patch('deploy_stack.get_remote_machines', autospec=True, 488 return_value=self.fake_remote_machines()) as gm_mock: 489 with patch('deploy_stack._can_run_ssh', lambda: False): 490 with patch('deploy_stack.copy_remote_logs', 491 autospec=True) as crl_mock: 492 with patch('deploy_stack.archive_logs', 493 autospec=True) as al_mock: 494 env = JujuData('foo', {'type': 'nonlocal'}) 495 client = ModelClient(env, '1.234-76', None) 496 dump_env_logs(client, '10.10.0.1', artifacts_dir) 497 al_mock.assert_called_once_with(artifacts_dir) 498 self.assertEqual( 499 ['machine-2'], 500 sorted(os.listdir(artifacts_dir))) 501 self.assertEqual((client, {'0': '10.10.0.1'}), gm_mock.call_args[0]) 502 self.assertEqual( 503 [(self.r2, '%s/machine-2' % artifacts_dir)], 504 [cal[0] for cal in crl_mock.call_args_list]) 505 self.assertEqual( 506 ['INFO No ssh, skipping logs for machine-0 using ' + repr(self.r0), 507 'INFO No ssh, skipping logs for machine-1 using ' + repr(self.r1), 508 'INFO Retrieving logs for machine-2 using ' + repr(self.r2)], 509 self.log_stream.getvalue().splitlines()) 510 511 def test_archive_logs(self): 512 with temp_dir() as log_dir: 513 with open(os.path.join(log_dir, 'fake.log'), 'w') as f: 514 f.write('log contents') 515 with patch('subprocess.check_call', autospec=True) as cc_mock: 516 archive_logs(log_dir) 517 log_path = os.path.join(log_dir, 'fake.log') 518 cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path]) 519 520 def test_archive_logs_syslog(self): 521 with temp_dir() as log_dir: 522 log_path = os.path.join(log_dir, 'syslog') 523 with open(log_path, 'w') as f: 524 f.write('syslog contents') 525 with patch('subprocess.check_call', autospec=True) as cc_mock: 526 archive_logs(log_dir) 527 cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path]) 528 529 def test_archive_logs_subdir(self): 530 with temp_dir() as log_dir: 531 subdir = os.path.join(log_dir, "subdir") 532 os.mkdir(subdir) 533 with open(os.path.join(subdir, 'fake.log'), 'w') as f: 534 f.write('log contents') 535 with patch('subprocess.check_call', autospec=True) as cc_mock: 536 archive_logs(log_dir) 537 log_path = os.path.join(subdir, 'fake.log') 538 cc_mock.assert_called_once_with(['gzip', '--best', '-f', log_path]) 539 540 def test_archive_logs_none(self): 541 with temp_dir() as log_dir: 542 with patch('subprocess.check_call', autospec=True) as cc_mock: 543 archive_logs(log_dir) 544 self.assertEquals(cc_mock.call_count, 0) 545 546 def test_archive_logs_multiple(self): 547 with temp_dir() as log_dir: 548 log_paths = [] 549 with open(os.path.join(log_dir, 'fake.log'), 'w') as f: 550 f.write('log contents') 551 log_paths.append(os.path.join(log_dir, 'fake.log')) 552 subdir = os.path.join(log_dir, "subdir") 553 os.mkdir(subdir) 554 with open(os.path.join(subdir, 'syslog'), 'w') as f: 555 f.write('syslog contents') 556 log_paths.append(os.path.join(subdir, 'syslog')) 557 with patch('subprocess.check_call', autospec=True) as cc_mock: 558 archive_logs(log_dir) 559 self.assertEqual(1, cc_mock.call_count) 560 call_args, call_kwargs = cc_mock.call_args 561 gzip_args = call_args[0] 562 self.assertEqual(0, len(call_kwargs)) 563 self.assertEqual(gzip_args[:3], ['gzip', '--best', '-f']) 564 self.assertEqual(set(gzip_args[3:]), set(log_paths)) 565 566 def test_copy_remote_logs(self): 567 # To get the logs, their permissions must be updated first, 568 # then downloaded in the order that they will be created 569 # to ensure errors do not prevent some logs from being retrieved. 570 with patch('deploy_stack.wait_for_port', autospec=True): 571 with patch('subprocess.check_output') as cc_mock: 572 copy_remote_logs(remote_from_address('10.10.0.1'), '/foo') 573 self.assertEqual( 574 (get_timeout_prefix(120) + ( 575 'ssh', 576 '-o', 'User ubuntu', 577 '-o', 'UserKnownHostsFile /dev/null', 578 '-o', 'StrictHostKeyChecking no', 579 '-o', 'PasswordAuthentication no', 580 '10.10.0.1', 581 'sudo chmod -Rf go+r /var/log/cloud-init*.log' 582 ' /var/log/juju/*.log' 583 ' /var/lib/juju/containers/juju-*-lxc-*/' 584 ' /var/log/lxd/juju-*' 585 ' /var/log/lxd/lxd.log' 586 ' /var/log/syslog' 587 ' /var/log/mongodb/mongodb.log' 588 ' /etc/network/interfaces' 589 ' /etc/environment' 590 ' /home/ubuntu/ifconfig.log' 591 ),), 592 cc_mock.call_args_list[0][0]) 593 self.assertEqual( 594 (get_timeout_prefix(120) + ( 595 'ssh', 596 '-o', 'User ubuntu', 597 '-o', 'UserKnownHostsFile /dev/null', 598 '-o', 'StrictHostKeyChecking no', 599 '-o', 'PasswordAuthentication no', 600 '10.10.0.1', 601 'ifconfig > /home/ubuntu/ifconfig.log'),), 602 cc_mock.call_args_list[1][0]) 603 self.assertEqual( 604 (get_timeout_prefix(120) + ( 605 'scp', '-rC', 606 '-o', 'User ubuntu', 607 '-o', 'UserKnownHostsFile /dev/null', 608 '-o', 'StrictHostKeyChecking no', 609 '-o', 'PasswordAuthentication no', 610 '10.10.0.1:/var/log/cloud-init*.log', 611 '10.10.0.1:/var/log/juju/*.log', 612 '10.10.0.1:/var/lib/juju/containers/juju-*-lxc-*/', 613 '10.10.0.1:/var/log/lxd/juju-*', 614 '10.10.0.1:/var/log/lxd/lxd.log', 615 '10.10.0.1:/var/log/syslog', 616 '10.10.0.1:/var/log/mongodb/mongodb.log', 617 '10.10.0.1:/etc/network/interfaces', 618 '10.10.0.1:/etc/environment', 619 '10.10.0.1:/home/ubuntu/ifconfig.log', 620 '/foo'),), 621 cc_mock.call_args_list[2][0]) 622 623 def test_copy_remote_logs_windows(self): 624 with _fake_winrm_certs(): 625 remote = remote_from_address('10.10.0.1', series="win2012hvr2") 626 with patch.object(remote, "copy", autospec=True) as copy_mock: 627 copy_remote_logs(remote, '/foo') 628 paths = [ 629 "%ProgramFiles(x86)%\\Cloudbase Solutions\\Cloudbase-Init\\log\\*", 630 "C:\\Juju\\log\\juju\\*.log", 631 ] 632 copy_mock.assert_called_once_with("/foo", paths) 633 634 def test_copy_remote_logs_with_errors(self): 635 # Ssh and scp errors will happen when /var/log/juju doesn't exist yet, 636 # but we log the case anc continue to retrieve as much as we can. 637 def remote_op(*args, **kwargs): 638 if 'ssh' in args: 639 raise subprocess.CalledProcessError('ssh error', 'output') 640 else: 641 raise subprocess.CalledProcessError('scp error', 'output') 642 643 with patch('subprocess.check_output', side_effect=remote_op) as co: 644 with patch('deploy_stack.wait_for_port', autospec=True): 645 copy_remote_logs(remote_from_address('10.10.0.1'), '/foo') 646 self.assertEqual(3, co.call_count) 647 self.assertEqual( 648 ["DEBUG ssh -o 'User ubuntu' -o 'UserKnownHostsFile /dev/null' " 649 "-o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' " 650 "10.10.0.1 'sudo chmod -Rf go+r /var/log/cloud-init*.log " 651 "/var/log/juju/*.log /var/lib/juju/containers/juju-*-lxc-*/ " 652 "/var/log/lxd/juju-* " 653 "/var/log/lxd/lxd.log " 654 "/var/log/syslog " 655 "/var/log/mongodb/mongodb.log " 656 "/etc/network/interfaces " 657 "/etc/environment " 658 "/home/ubuntu/ifconfig.log'", 659 'WARNING Could not allow access to the juju logs:', 660 'WARNING None', 661 "DEBUG ssh -o 'User ubuntu' -o 'UserKnownHostsFile /dev/null' " 662 "-o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' " 663 "10.10.0.1 'ifconfig > /home/ubuntu/ifconfig.log'", 664 'WARNING Could not capture ifconfig state:', 665 'WARNING None', 'WARNING Could not retrieve some or all logs:', 666 'WARNING CalledProcessError()', 667 ], 668 self.log_stream.getvalue().splitlines()) 669 670 def test_get_machines_for_logs(self): 671 client = ModelClient( 672 JujuData('cloud', {'type': 'ec2'}), '1.23.4', None) 673 status = Status.from_text("""\ 674 machines: 675 "0": 676 dns-name: 10.11.12.13 677 "1": 678 dns-name: 10.11.12.14 679 """) 680 with patch.object(client, 'get_status', autospec=True, 681 return_value=status): 682 machines = get_remote_machines(client, {}) 683 self.assert_machines( 684 {'0': '10.11.12.13', '1': '10.11.12.14'}, machines) 685 686 def test_get_machines_for_logs_with_bootstrap_host(self): 687 client = ModelClient( 688 JujuData('cloud', {'type': 'ec2'}), '1.23.4', None) 689 status = Status.from_text("""\ 690 machines: 691 "0": 692 instance-id: pending 693 """) 694 with patch.object(client, 'get_status', autospec=True, 695 return_value=status): 696 machines = get_remote_machines(client, {'0': '10.11.111.222'}) 697 self.assert_machines({'0': '10.11.111.222'}, machines) 698 699 def test_get_machines_for_logs_with_no_addresses(self): 700 client = ModelClient( 701 JujuData('cloud', {'type': 'ec2'}), '1.23.4', None) 702 with patch.object(client, 'get_status', autospec=True, 703 side_effect=Exception): 704 machines = get_remote_machines(client, {'0': '10.11.111.222'}) 705 self.assert_machines({'0': '10.11.111.222'}, machines) 706 707 @patch('subprocess.check_call') 708 def test_get_remote_machines_with_maas(self, cc_mock): 709 config = { 710 'type': 'maas', 711 'name': 'foo', 712 'maas-server': 'http://bar/MASS/', 713 } 714 juju_data = JujuData('cloud', config) 715 cloud_name = 'mycloud' 716 juju_data.clouds = {'clouds': {cloud_name: { 717 'endpoint': config['maas-server'], 718 'type': config['type'], 719 }}} 720 juju_data.credentials = {'credentials': {cloud_name: {'credentials': { 721 'maas-oauth': 'baz', 722 }}}} 723 client = ModelClient(juju_data, '1.23.4', None) 724 status = Status.from_text("""\ 725 machines: 726 "0": 727 dns-name: node1.maas 728 "1": 729 dns-name: node2.maas 730 """) 731 with patch.object(client, 'get_status', autospec=True, 732 return_value=status): 733 allocated_ips = { 734 'node1.maas': '10.11.12.13', 735 'node2.maas': '10.11.12.14', 736 } 737 with patch('substrate.MAASAccount.get_allocated_ips', 738 autospec=True, return_value=allocated_ips): 739 machines = get_remote_machines(client, {'0': 'node1.maas'}) 740 self.assert_machines( 741 {'0': '10.11.12.13', '1': '10.11.12.14'}, machines) 742 743 def test_iter_remote_machines(self): 744 client = ModelClient( 745 JujuData('cloud', {'type': 'ec2'}), '1.23.4', None) 746 status = Status.from_text("""\ 747 machines: 748 "0": 749 dns-name: 10.11.12.13 750 "1": 751 dns-name: 10.11.12.14 752 """) 753 with patch.object(client, 'get_status', autospec=True, 754 return_value=status): 755 machines = [(m, r.address) 756 for m, r in iter_remote_machines(client)] 757 self.assertEqual( 758 [('0', '10.11.12.13'), ('1', '10.11.12.14')], machines) 759 760 def test_iter_remote_machines_with_series(self): 761 client = ModelClient( 762 JujuData('cloud', {'type': 'ec2'}), '1.23.4', None) 763 status = Status.from_text("""\ 764 machines: 765 "0": 766 dns-name: 10.11.12.13 767 series: trusty 768 "1": 769 dns-name: 10.11.12.14 770 series: win2012hvr2 771 """) 772 with patch.object(client, 'get_status', autospec=True, 773 return_value=status): 774 with _fake_winrm_certs(): 775 machines = [(m, r.address, r.series) 776 for m, r in iter_remote_machines(client)] 777 self.assertEqual( 778 [('0', '10.11.12.13', 'trusty'), 779 ('1', '10.11.12.14', 'win2012hvr2')], machines) 780 781 782 class TestDeployDummyStack(FakeHomeTestCase): 783 784 def test_deploy_dummy_stack_sets_centos_constraints(self): 785 env = JujuData('foo', {'type': 'maas'}) 786 client = ModelClient(env, '2.0.0', '/foo/juju') 787 with patch('subprocess.check_call', autospec=True) as cc_mock: 788 with patch.object(ModelClient, 'wait_for_started'): 789 with patch('deploy_stack.get_random_string', 790 return_value='fake-token', autospec=True): 791 deploy_dummy_stack(client, 'centos') 792 assert_juju_call(self, cc_mock, client, 793 ('juju', '--show-log', 'set-model-constraints', '-m', 794 'foo:foo', 'tags=MAAS_NIC_1'), 0) 795 796 def test_assess_juju_relations(self): 797 env = JujuData('foo', {'type': 'nonlocal'}) 798 client = ModelClient(env, None, '/foo/juju') 799 with patch.object(client, 'get_juju_output', side_effect='fake-token', 800 autospec=True): 801 with patch('subprocess.check_call', autospec=True) as cc_mock: 802 with patch('deploy_stack.get_random_string', 803 return_value='fake-token', autospec=True): 804 with patch('deploy_stack.check_token', 805 autospec=True) as ct_mock: 806 assess_juju_relations(client) 807 assert_juju_call(self, cc_mock, client, ( 808 'juju', '--show-log', 'config', '-m', 'foo:foo', 809 'dummy-source', 'token=fake-token'), 0) 810 ct_mock.assert_called_once_with(client, 'fake-token') 811 812 def test_deploy_dummy_stack_centos(self): 813 client = fake_juju_client() 814 client.bootstrap() 815 with patch.object(client, 'deploy', autospec=True) as dp_mock: 816 with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'): 817 deploy_dummy_stack(client, 'centos7') 818 calls = [ 819 call('/tmp/repo/charms-centos/dummy-source', series='centos7'), 820 call('/tmp/repo/charms-centos/dummy-sink', series='centos7')] 821 self.assertEqual(dp_mock.mock_calls, calls) 822 823 def test_deploy_dummy_stack_win(self): 824 client = fake_juju_client() 825 client.bootstrap() 826 with patch.object(client, 'deploy', autospec=True) as dp_mock: 827 with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'): 828 deploy_dummy_stack(client, 'win2012hvr2') 829 calls = [ 830 call('/tmp/repo/charms-win/dummy-source', series='win2012hvr2'), 831 call('/tmp/repo/charms-win/dummy-sink', series='win2012hvr2')] 832 self.assertEqual(dp_mock.mock_calls, calls) 833 834 def test_deploy_dummy_stack_charmstore(self): 835 client = fake_juju_client() 836 client.bootstrap() 837 with patch.object(client, 'deploy', autospec=True) as dp_mock: 838 deploy_dummy_stack(client, 'bionic', use_charmstore=True) 839 calls = [ 840 call('cs:~juju-qa/dummy-source', series='bionic'), 841 call('cs:~juju-qa/dummy-sink', series='bionic')] 842 self.assertEqual(dp_mock.mock_calls, calls) 843 844 def test_deploy_dummy_stack(self): 845 env = JujuData('foo', {'type': 'nonlocal'}) 846 client = ModelClient(env, '2.0.0', '/foo/juju') 847 status = yaml.safe_dump({ 848 'machines': {'0': {'agent-state': 'started'}}, 849 'services': { 850 'dummy-sink': {'units': { 851 'dummy-sink/0': {'agent-state': 'started'}} 852 } 853 } 854 }) 855 856 def output(*args, **kwargs): 857 token_file = '/var/run/dummy-sink/token' 858 output = { 859 ('show-status', '--format', 'yaml'): status, 860 ('ssh', 'dummy-sink/0', 'cat', token_file): 'fake-token', 861 } 862 return output[args] 863 864 with patch.object(client, 'get_juju_output', side_effect=output, 865 autospec=True) as gjo_mock: 866 with patch('subprocess.check_call', autospec=True) as cc_mock: 867 with patch('deploy_stack.get_random_string', 868 return_value='fake-token', autospec=True): 869 with patch('sys.stdout', autospec=True): 870 with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'): 871 deploy_dummy_stack(client, 'bar-') 872 assert_juju_call(self, cc_mock, client, ( 873 'juju', '--show-log', 'deploy', '-m', 'foo:foo', 874 '/tmp/repo/charms/dummy-source', '--series', 'bar-'), 0) 875 assert_juju_call(self, cc_mock, client, ( 876 'juju', '--show-log', 'deploy', '-m', 'foo:foo', 877 '/tmp/repo/charms/dummy-sink', '--series', 'bar-'), 1) 878 assert_juju_call(self, cc_mock, client, ( 879 'juju', '--show-log', 'add-relation', '-m', 'foo:foo', 880 'dummy-source', 'dummy-sink'), 2) 881 assert_juju_call(self, cc_mock, client, ( 882 'juju', '--show-log', 'expose', '-m', 'foo:foo', 'dummy-sink'), 3) 883 self.assertEqual(cc_mock.call_count, 4) 884 self.assertEqual( 885 [ 886 call('show-status', '--format', 'yaml', controller=False) 887 ], 888 gjo_mock.call_args_list) 889 890 client = client.clone(version='1.25.0') 891 with patch.object(client, 'get_juju_output', side_effect=output, 892 autospec=True) as gjo_mock: 893 with patch('subprocess.check_call', autospec=True) as cc_mock: 894 with patch('deploy_stack.get_random_string', 895 return_value='fake-token', autospec=True): 896 with patch('sys.stdout', autospec=True): 897 with temp_os_env('JUJU_REPOSITORY', '/tmp/repo'): 898 deploy_dummy_stack(client, 'bar-') 899 assert_juju_call(self, cc_mock, client, ( 900 'juju', '--show-log', 'deploy', '-m', 'foo:foo', 901 'local:bar-/dummy-source', '--series', 'bar-'), 0) 902 assert_juju_call(self, cc_mock, client, ( 903 'juju', '--show-log', 'deploy', '-m', 'foo:foo', 904 'local:bar-/dummy-sink', '--series', 'bar-'), 1) 905 906 907 def fake_ModelClient(env, path=None, debug=None): 908 return ModelClient(env=env, version='1.2.3.4', full_path=path) 909 910 911 class FakeBootstrapManager: 912 913 def __init__(self, client, keep_env=False): 914 self.client = client 915 self.tear_down_client = client 916 self.entered_top = False 917 self.exited_top = False 918 self.entered_bootstrap = False 919 self.exited_bootstrap = False 920 self.entered_runtime = False 921 self.exited_runtime = False 922 self.torn_down = False 923 self.known_hosts = {'0': '0.example.org'} 924 self.keep_env = keep_env 925 926 @contextmanager 927 def top_context(self): 928 try: 929 self.entered_top = True 930 yield ['bar'] 931 finally: 932 self.exited_top = True 933 934 @contextmanager 935 def bootstrap_context(self, machines): 936 initial_home = self.client.env.juju_home 937 self.client.env.environment = self.client.env.environment + '-temp' 938 self.client.env.controller.name = self.client.env.environment 939 try: 940 self.entered_bootstrap = True 941 self.client.env.juju_home = os.path.join(initial_home, 'isolated') 942 self.client.bootstrap() 943 yield 944 finally: 945 self.exited_bootstrap = True 946 if not self.permanent: 947 self.client.env.juju_home = initial_home 948 949 @contextmanager 950 def runtime_context(self, machines): 951 try: 952 self.entered_runtime = True 953 yield 954 finally: 955 if not self.keep_env: 956 self.tear_down() 957 self.exited_runtime = True 958 959 def tear_down(self): 960 self.tear_down_client.kill_controller() 961 self.torn_down = True 962 963 @contextmanager 964 def booted_context(self, upload_tools, **kwargs): 965 with self.top_context() as machines: 966 with self.bootstrap_context(machines): 967 self.client.bootstrap(upload_tools) 968 with self.runtime_context(machines): 969 yield machines 970 971 972 class TestDeployJob(FakeHomeTestCase): 973 974 @contextmanager 975 def ds_cxt(self): 976 env = JujuData('foo', {}) 977 client = fake_ModelClient(env) 978 bc_cxt = patch('deploy_stack.client_from_config', 979 return_value=client) 980 fc_cxt = patch('jujupy.JujuData.from_config', 981 return_value=env) 982 mgr = MagicMock() 983 bm_cxt = patch('deploy_stack.BootstrapManager', autospec=True, 984 return_value=mgr) 985 juju_cxt = patch('jujupy.ModelClient.juju', autospec=True) 986 ajr_cxt = patch('deploy_stack.assess_juju_run', autospec=True) 987 dds_cxt = patch('deploy_stack.deploy_dummy_stack', autospec=True) 988 with bc_cxt, fc_cxt, bm_cxt as bm_mock, juju_cxt, ajr_cxt, dds_cxt: 989 yield client, bm_mock 990 991 def test_region(self): 992 args = Namespace( 993 env='base', juju_bin='/fake/juju', logs='log', temp_env_name='foo', 994 charm_prefix=None, bootstrap_host=None, machine=None, 995 series='trusty', debug=False, agent_url=None, agent_stream=None, 996 keep_env=False, upload_tools=False, 997 region='region-foo', verbose=False, upgrade=False, deadline=None, 998 controller_host=None, use_charmstore=False, to=None) 999 with self.ds_cxt() as (client, bm_mock): 1000 with patch('deploy_stack.assess_juju_relations', 1001 autospec=True): 1002 with patch('subprocess.Popen', autospec=True, 1003 return_value=FakePopen('', '', 0)): 1004 with patch('deploy_stack.make_controller_strategy', 1005 ) as mcs_mock: 1006 _deploy_job(args, 'local:trusty/', 'trusty') 1007 bm_mock.assert_called_once_with( 1008 'foo', client, client, None, None, 'trusty', None, None, 1009 'region-foo', 'log', False, 1010 controller_strategy=mcs_mock.return_value) 1011 1012 def test_deploy_job_changes_series_with_win(self): 1013 args = Namespace( 1014 series='windows', temp_env_name='windows', env=None, upgrade=None, 1015 charm_prefix=None, bootstrap_host=None, machine=None, logs=None, 1016 debug=None, juju_bin=None, agent_url=None, agent_stream=None, 1017 keep_env=None, upload_tools=None, 1018 region=None, verbose=None, use_charmstore=False, to=None) 1019 with patch('deploy_stack.deploy_job_parse_args', return_value=args, 1020 autospec=True): 1021 with patch('deploy_stack._deploy_job', autospec=True) as ds_mock: 1022 deploy_job() 1023 ds_mock.assert_called_once_with(args, 'windows', 'trusty') 1024 1025 def test_deploy_job_changes_series_with_centos(self): 1026 args = Namespace( 1027 series='centos', temp_env_name='centos', env=None, upgrade=None, 1028 charm_prefix=None, bootstrap_host=None, machine=None, logs=None, 1029 debug=None, juju_bin=None, agent_url=None, agent_stream=None, 1030 keep_env=None, upload_tools=None, 1031 region=None, verbose=None, use_charmstore=False, to=None) 1032 with patch('deploy_stack.deploy_job_parse_args', return_value=args, 1033 autospec=True): 1034 with patch('deploy_stack._deploy_job', autospec=True) as ds_mock: 1035 deploy_job() 1036 ds_mock.assert_called_once_with(args, 'centos', 'trusty') 1037 1038 1039 class TestTestUpgrade(FakeHomeTestCase): 1040 1041 RUN_UNAME = ( 1042 'juju', '--show-log', 'run', '-e', 'foo', '--format', 'json', 1043 '--service', 'dummy-source,dummy-sink', 'uname') 1044 STATUS = ( 1045 'juju', '--show-log', 'show-status', '-m', 'foo:foo', 1046 '--format', 'yaml') 1047 CONTROLLER_STATUS = ( 1048 'juju', '--show-log', 'show-status', '-m', 'foo:controller', 1049 '--format', 'yaml') 1050 GET_ENV = ('juju', '--show-log', 'model-config', '-m', 'foo:foo', 1051 'agent-metadata-url') 1052 GET_CONTROLLER_ENV = ( 1053 'juju', '--show-log', 'model-config', '-m', 'foo:controller', 1054 'agent-metadata-url') 1055 LIST_MODELS = ( 1056 'juju', '--show-log', 'list-models', '-c', 'foo', '--format', 'yaml') 1057 1058 @classmethod 1059 def upgrade_output(cls, args, **kwargs): 1060 status = yaml.safe_dump({ 1061 'machines': {'0': { 1062 'agent-state': 'started', 1063 'agent-version': '2.0-rc2'}}, 1064 'services': {}}) 1065 juju_run_out = json.dumps([ 1066 {"MachineId": "1", "Stdout": "Linux\n"}, 1067 {"MachineId": "2", "Stdout": "Linux\n"}]) 1068 list_models = json.dumps( 1069 {'models': [ 1070 {'name': 'controller'}, 1071 {'name': 'foo'}, 1072 ]}) 1073 output = { 1074 cls.STATUS: status, 1075 cls.CONTROLLER_STATUS: status, 1076 cls.RUN_UNAME: juju_run_out, 1077 cls.GET_ENV: 'testing', 1078 cls.GET_CONTROLLER_ENV: 'testing', 1079 cls.LIST_MODELS: list_models, 1080 } 1081 return FakePopen(output[args], '', 0) 1082 1083 @contextmanager 1084 def upgrade_mocks(self): 1085 with patch('subprocess.Popen', side_effect=self.upgrade_output, 1086 autospec=True) as co_mock: 1087 with patch('subprocess.check_call', autospec=True) as cc_mock: 1088 with patch('deploy_stack.check_token', autospec=True): 1089 with patch('deploy_stack.get_random_string', 1090 return_value="FAKETOKEN", autospec=True): 1091 with patch('jujupy.ModelClient.get_version', 1092 side_effect=lambda cls: 1093 '2.0-rc2-arch-series'): 1094 with patch( 1095 'jujupy.backend.get_timeout_prefix', 1096 autospec=True, return_value=()): 1097 yield (co_mock, cc_mock) 1098 1099 def test_assess_upgrade(self): 1100 env = JujuData('foo', {'type': 'foo'}) 1101 old_client = ModelClient(env, None, '/foo/juju') 1102 with self.upgrade_mocks() as (co_mock, cc_mock): 1103 assess_upgrade(old_client, '/bar/juju') 1104 new_client = ModelClient(env, None, '/bar/juju') 1105 # Needs to upgrade the controller first. 1106 assert_juju_call(self, cc_mock, new_client, ( 1107 'juju', '--show-log', 'upgrade-juju', '-m', 'foo:controller', 1108 '--agent-version', '2.0-rc2'), 0) 1109 assert_juju_call(self, cc_mock, new_client, ( 1110 'juju', '--show-log', 'show-status', '-m', 'foo:controller', 1111 '--format', 'yaml'), 1) 1112 assert_juju_call(self, cc_mock, new_client, ( 1113 'juju', '--show-log', 'list-models', '-c', 'foo'), 2) 1114 assert_juju_call(self, cc_mock, new_client, ( 1115 'juju', '--show-log', 'upgrade-juju', '-m', 'foo:foo', 1116 '--agent-version', '2.0-rc2'), 3) 1117 self.assertEqual(cc_mock.call_count, 6) 1118 assert_juju_call(self, co_mock, new_client, self.LIST_MODELS, 0) 1119 assert_juju_call(self, co_mock, new_client, self.GET_CONTROLLER_ENV, 1) 1120 assert_juju_call(self, co_mock, new_client, self.GET_CONTROLLER_ENV, 2) 1121 assert_juju_call(self, co_mock, new_client, self.CONTROLLER_STATUS, 3) 1122 assert_juju_call(self, co_mock, new_client, self.GET_ENV, 5) 1123 assert_juju_call(self, co_mock, new_client, self.GET_ENV, 6) 1124 assert_juju_call(self, co_mock, new_client, self.STATUS, 7) 1125 self.assertEqual(co_mock.call_count, 9) 1126 1127 def test__get_clients_to_upgrade_returns_controller_and_model(self): 1128 old_client = fake_juju_client() 1129 old_client.bootstrap() 1130 1131 with patch('jujupy.ModelClient.get_version', 1132 return_value='2.0-rc2-arch-series'): 1133 new_clients = _get_clients_to_upgrade( 1134 old_client, '/foo/newer/juju') 1135 1136 self.assertEqual(len(new_clients), 2) 1137 self.assertEqual(new_clients[0].model_name, 'controller') 1138 self.assertEqual(new_clients[1].model_name, 'name') 1139 1140 def test_mass_timeout(self): 1141 config = {'type': 'foo'} 1142 old_client = ModelClient(JujuData('foo', config), None, '/foo/juju') 1143 with self.upgrade_mocks(): 1144 with patch.object(ModelClient, 'wait_for_version') as wfv_mock: 1145 assess_upgrade(old_client, '/bar/juju') 1146 wfv_mock.assert_has_calls([call('2.0-rc2', 600)] * 2) 1147 config['type'] = 'maas' 1148 with patch.object(ModelClient, 'wait_for_version') as wfv_mock: 1149 assess_upgrade(old_client, '/bar/juju') 1150 wfv_mock.assert_has_calls([call('2.0-rc2', 1200)] * 2) 1151 1152 1153 class TestMakeControllerStrategy(TestCase): 1154 1155 def test_make_controller_strategy_no_host(self): 1156 client = object() 1157 tear_down_client = object() 1158 strategy = make_controller_strategy(client, tear_down_client, None) 1159 self.assertIs(CreateController, type(strategy)) 1160 self.assertEqual(client, strategy.client) 1161 self.assertEqual(tear_down_client, strategy.tear_down_client) 1162 1163 def test_make_controller_strategy_host(self): 1164 client = object() 1165 tear_down_client = object() 1166 with patch.dict(os.environ, { 1167 'SSO_EMAIL': 'sso@email', 1168 'SSO_PASSWORD': 'sso-password'}): 1169 strategy = make_controller_strategy(client, tear_down_client, 1170 'host') 1171 self.assertIs(PublicController, type(strategy)) 1172 self.assertEqual(client, strategy.client) 1173 self.assertEqual(tear_down_client, strategy.tear_down_client) 1174 self.assertEqual('sso@email', strategy.email) 1175 self.assertEqual('sso-password', strategy.password) 1176 1177 1178 class TestExistingController(TestCase): 1179 1180 def get_controller(self): 1181 client = fake_juju_client() 1182 create_controller = ExistingController(client) 1183 return create_controller 1184 1185 def test_prepare(self): 1186 controller = self.get_controller() 1187 controller.prepare('foo') 1188 self.assertEqual('foo', controller.client.env.controller.name) 1189 1190 def test_create_initial_model(self): 1191 controller = self.get_controller() 1192 client = controller.client 1193 self.assertEqual({'models': []}, client.get_models()) 1194 controller.create_initial_model() 1195 self.assertEqual([{'name': 'name'}], client.get_models()['models']) 1196 1197 def test_tear_down(self): 1198 controller = self.get_controller() 1199 client = controller.client 1200 client.add_model(client.env) 1201 self.assertEqual([{'name': 'name'}], client.get_models()['models']) 1202 controller.tear_down('') 1203 self.assertEqual([], client.get_models()['models']) 1204 1205 1206 class TestCreateController(FakeHomeTestCase): 1207 1208 def get_cleanup_controller(self): 1209 client = fake_juju_client() 1210 create_controller = CreateController(None, client) 1211 return create_controller 1212 1213 def get_controller(self): 1214 client = fake_juju_client() 1215 create_controller = CreateController(client, None) 1216 return create_controller 1217 1218 def test_prepare_no_existing(self): 1219 create_controller = self.get_cleanup_controller() 1220 client = create_controller.tear_down_client 1221 create_controller.prepare() 1222 self.assertEqual({'models': []}, client.get_models()) 1223 self.assertEqual( 1224 'not-bootstrapped', client._backend.controller_state.state) 1225 1226 def test_prepare_leftover(self): 1227 create_controller = self.get_cleanup_controller() 1228 client = create_controller.tear_down_client 1229 client.bootstrap() 1230 create_controller.prepare() 1231 self.assertEqual({'models': []}, client.get_models()) 1232 self.assertEqual( 1233 'controller-killed', client._backend.controller_state.state) 1234 1235 def test_create_initial_model(self): 1236 controller = self.get_controller() 1237 client = controller.client 1238 self.assertEqual({'models': []}, client.get_models()) 1239 controller.create_initial_model(False, 'angsty', {}) 1240 self.assertItemsEqual([{'name': 'controller'}, {'name': 'name'}], 1241 client.get_models()['models']) 1242 self.assertEqual( 1243 'bootstrapped', client._backend.controller_state.state) 1244 1245 def test_get_hosts(self): 1246 controller = self.get_controller() 1247 client = controller.client 1248 client.bootstrap() 1249 self.assertEqual({'0': '0.example.com'}, controller.get_hosts()) 1250 1251 def test_tear_down_existing(self): 1252 create_controller = self.get_cleanup_controller() 1253 client = create_controller.tear_down_client 1254 client.bootstrap() 1255 create_controller.tear_down(True) 1256 self.assertEqual({'models': []}, client.get_models()) 1257 self.assertEqual( 1258 'controller-destroyed', client._backend.controller_state.state) 1259 1260 def test_tear_down_existing_no_controller(self): 1261 create_controller = self.get_cleanup_controller() 1262 client = create_controller.tear_down_client 1263 client.bootstrap() 1264 create_controller.tear_down(False) 1265 self.assertEqual({'models': []}, client.get_models()) 1266 self.assertEqual( 1267 'controller-killed', client._backend.controller_state.state) 1268 1269 def test_tear_down_nothing(self): 1270 create_controller = self.get_cleanup_controller() 1271 with self.assertRaises(subprocess.CalledProcessError): 1272 create_controller.tear_down(True) 1273 1274 1275 class TestPublicController(FakeHomeTestCase): 1276 1277 def get_cleanup_controller(self): 1278 client = fake_juju_client() 1279 public_controller = PublicController('host', 'email2', 'password2', 1280 None, client) 1281 return public_controller 1282 1283 def get_controller(self): 1284 client = fake_juju_client() 1285 public_controller = PublicController('host', 'email2', 'password2', 1286 client, None) 1287 return public_controller 1288 1289 def test_prepare_no_existing(self): 1290 public_controller = self.get_cleanup_controller() 1291 client = public_controller.tear_down_client 1292 public_controller.prepare() 1293 self.assertEqual({'models': []}, client.get_models()) 1294 self.assertEqual( 1295 'not-bootstrapped', client._backend.controller_state.state) 1296 1297 def test_prepare_leftover(self): 1298 public_controller = self.get_cleanup_controller() 1299 client = public_controller.tear_down_client 1300 client.add_model(client.env) 1301 public_controller.prepare() 1302 self.assertEqual({'models': []}, client.get_models()) 1303 self.assertEqual( 1304 'model-destroyed', client._backend.controller_state.state) 1305 1306 def test_create_initial_model(self): 1307 controller = self.get_controller() 1308 client = controller.client 1309 self.assertEqual({'models': []}, client.get_models()) 1310 controller.create_initial_model(False, 'angsty', {}) 1311 self.assertItemsEqual([{'name': 'name'}], 1312 client.get_models()['models']) 1313 self.assertEqual( 1314 'created', client._backend.controller_state.state) 1315 1316 def test_get_hosts(self): 1317 controller = self.get_controller() 1318 client = controller.client 1319 client.bootstrap() 1320 self.assertEqual({}, controller.get_hosts()) 1321 1322 def test_tear_down_existing(self): 1323 public_controller = self.get_cleanup_controller() 1324 client = public_controller.tear_down_client 1325 client.add_model(client.env) 1326 public_controller.tear_down(True) 1327 self.assertEqual({'models': []}, client.get_models()) 1328 self.assertEqual( 1329 'model-destroyed', client._backend.controller_state.state) 1330 1331 def test_tear_down_nothing(self): 1332 public_controller = self.get_cleanup_controller() 1333 with self.assertRaises(subprocess.CalledProcessError): 1334 public_controller.tear_down(True) 1335 1336 1337 class TestBootstrapManager(FakeHomeTestCase): 1338 1339 def test_from_args(self): 1340 deadline = datetime(2012, 11, 10, 9, 8, 7) 1341 args = Namespace( 1342 env='foo', juju_bin='bar', debug=True, temp_env_name='baz', 1343 bootstrap_host='example.org', machine=['example.com'], 1344 series='angsty', agent_url='qux', agent_stream='escaped', 1345 region='eu-west-northwest-5', logs='pine', keep_env=True, 1346 deadline=deadline, to=None) 1347 with patch('deploy_stack.client_from_config') as fc_mock: 1348 bs_manager = BootstrapManager.from_args(args) 1349 fc_mock.assert_called_once_with('foo', 'bar', debug=True, 1350 soft_deadline=deadline) 1351 self.assertEqual('baz', bs_manager.temp_env_name) 1352 self.assertIs(fc_mock.return_value, bs_manager.client) 1353 self.assertIs(fc_mock.return_value, bs_manager.tear_down_client) 1354 self.assertEqual('example.org', bs_manager.bootstrap_host) 1355 self.assertEqual(['example.com'], bs_manager.machines) 1356 self.assertEqual('angsty', bs_manager.series) 1357 self.assertEqual('qux', bs_manager.agent_url) 1358 self.assertEqual('escaped', bs_manager.agent_stream) 1359 self.assertEqual('eu-west-northwest-5', bs_manager.region) 1360 self.assertIs(True, bs_manager.keep_env) 1361 self.assertEqual('pine', bs_manager.log_dir) 1362 self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts) 1363 self.assertIsFalse(bs_manager.has_controller) 1364 1365 def test_from_existing(self): 1366 deadline = datetime(2012, 11, 10, 9, 8, 7) 1367 args = Namespace( 1368 env='foo', juju_bin='bar', debug=True, temp_env_name='baz', 1369 bootstrap_host='example.org', machine=['example.com'], 1370 series='angsty', agent_url='qux', agent_stream='escaped', 1371 region='eu-west-northwest-5', logs='pine', keep_env=True, 1372 deadline=deadline, to=None, existing='existing') 1373 with patch('deploy_stack.client_for_existing') as fc_mock: 1374 with patch.dict('os.environ', {'JUJU_DATA': 'foo'}): 1375 with patch('deploy_stack.os.path.isdir'): 1376 bs_manager = BootstrapManager._for_existing_controller( 1377 args) 1378 fc_mock.assert_called_once_with( 1379 'bar', 'foo', controller_name='existing', model_name='baz') 1380 self.assertEqual('baz', bs_manager.temp_env_name) 1381 self.assertIs(fc_mock.return_value, bs_manager.client) 1382 self.assertIs(fc_mock.return_value, bs_manager.tear_down_client) 1383 self.assertEqual('example.org', bs_manager.bootstrap_host) 1384 self.assertEqual(['example.com'], bs_manager.machines) 1385 self.assertEqual('angsty', bs_manager.series) 1386 self.assertEqual('qux', bs_manager.agent_url) 1387 self.assertEqual('escaped', bs_manager.agent_stream) 1388 self.assertEqual('eu-west-northwest-5', bs_manager.region) 1389 self.assertIs(True, bs_manager.keep_env) 1390 self.assertEqual('pine', bs_manager.log_dir) 1391 self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts) 1392 self.assertIsFalse(bs_manager.has_controller) 1393 1394 def test_from_client(self): 1395 deadline = datetime(2012, 11, 10, 9, 8, 7) 1396 args = Namespace( 1397 env='foo', juju_bin='bar', debug=True, temp_env_name='baz', 1398 bootstrap_host='example.org', machine=['example.com'], 1399 series='angsty', agent_url='qux', agent_stream='escaped', 1400 region='eu-west-northwest-5', logs='pine', keep_env=True, 1401 deadline=deadline, to=None) 1402 client = fake_juju_client() 1403 bs_manager = BootstrapManager.from_client(args, client) 1404 self.assertEqual('baz', bs_manager.temp_env_name) 1405 self.assertIs(client, bs_manager.client) 1406 self.assertEqual('example.org', bs_manager.bootstrap_host) 1407 self.assertEqual(['example.com'], bs_manager.machines) 1408 self.assertEqual('angsty', bs_manager.series) 1409 self.assertEqual('qux', bs_manager.agent_url) 1410 self.assertEqual('escaped', bs_manager.agent_stream) 1411 self.assertEqual('eu-west-northwest-5', bs_manager.region) 1412 self.assertIs(True, bs_manager.keep_env) 1413 self.assertEqual('pine', bs_manager.log_dir) 1414 self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts) 1415 self.assertIsFalse(bs_manager.has_controller) 1416 1417 def test_no_args(self): 1418 args = Namespace( 1419 env='foo', juju_bin='bar', debug=True, temp_env_name='baz', 1420 bootstrap_host='example.org', machine=['example.com'], 1421 series='angsty', agent_url='qux', agent_stream='escaped', 1422 region='eu-west-northwest-5', logs=None, keep_env=True, 1423 deadline=None, to=None) 1424 with patch('deploy_stack.client_from_config') as fc_mock: 1425 with patch('utility.os.makedirs'): 1426 bs_manager = BootstrapManager.from_args(args) 1427 fc_mock.assert_called_once_with('foo', 'bar', debug=True, 1428 soft_deadline=None) 1429 self.assertEqual('baz', bs_manager.temp_env_name) 1430 self.assertIs(fc_mock.return_value, bs_manager.client) 1431 self.assertIs(fc_mock.return_value, bs_manager.tear_down_client) 1432 self.assertEqual('example.org', bs_manager.bootstrap_host) 1433 self.assertEqual(['example.com'], bs_manager.machines) 1434 self.assertEqual('angsty', bs_manager.series) 1435 self.assertEqual('qux', bs_manager.agent_url) 1436 self.assertEqual('escaped', bs_manager.agent_stream) 1437 self.assertEqual('eu-west-northwest-5', bs_manager.region) 1438 self.assertIs(True, bs_manager.keep_env) 1439 logs_arg = bs_manager.log_dir.split("/") 1440 logs_ts = logs_arg[4] 1441 self.assertEqual(logs_arg[1:4], ['tmp', 'baz', 'logs']) 1442 self.assertTrue(logs_ts, datetime.strptime(logs_ts, "%Y%m%d%H%M%S")) 1443 self.assertEqual({'0': 'example.org'}, bs_manager.known_hosts) 1444 self.assertIsFalse(bs_manager.has_controller) 1445 1446 def test_from_args_no_host(self): 1447 args = Namespace( 1448 env='foo', juju_bin='bar', debug=True, temp_env_name='baz', 1449 bootstrap_host=None, machine=['example.com'], 1450 series='angsty', agent_url='qux', agent_stream='escaped', 1451 region='eu-west-northwest-5', logs='pine', keep_env=True, 1452 deadline=None, to=None) 1453 with patch('deploy_stack.client_from_config'): 1454 bs_manager = BootstrapManager.from_args(args) 1455 self.assertIs(None, bs_manager.bootstrap_host) 1456 self.assertEqual({}, bs_manager.known_hosts) 1457 1458 def make_client(self): 1459 client = MagicMock() 1460 client.env = JujuData( 1461 'foo', {'type': 'baz'}, use_context(self, temp_dir())) 1462 client.get_matching_agent_version.return_value = '3.14' 1463 client.get_cache_path.return_value = get_cache_path( 1464 client.env.juju_home) 1465 return client 1466 1467 def test_bootstrap_context_calls_update_env(self): 1468 client = fake_juju_client() 1469 client.env.juju_home = use_context(self, temp_dir()) 1470 ue_mock = use_context( 1471 self, patch('deploy_stack.update_env', wraps=update_env)) 1472 wfp_mock = use_context( 1473 self, patch('deploy_stack.wait_for_port', autospec=True)) 1474 bs_manager = BootstrapManager( 1475 'bar', client, client, None, 1476 [], 'wacky', 'url', 'devel', None, client.env.juju_home, False) 1477 bs_manager.known_hosts['0'] = 'bootstrap.example.org' 1478 with bs_manager.bootstrap_context([]): 1479 pass 1480 ue_mock.assert_called_with( 1481 client.env, 'bar', series='wacky', 1482 bootstrap_host='bootstrap.example.org', 1483 agent_url='url', agent_stream='devel', region=None) 1484 wfp_mock.assert_called_once_with( 1485 'bootstrap.example.org', 22, timeout=120) 1486 1487 def test_bootstrap_context_calls_update_env_omit(self): 1488 client = fake_juju_client() 1489 client.env.juju_home = use_context(self, temp_dir()) 1490 ue_mock = use_context( 1491 self, patch('deploy_stack.update_env', wraps=update_env)) 1492 wfp_mock = use_context( 1493 self, patch('deploy_stack.wait_for_port', autospec=True)) 1494 bs_manager = BootstrapManager( 1495 'bar', client, client, None, 1496 [], 'wacky', 'url', 'devel', None, client.env.juju_home, True) 1497 bs_manager.known_hosts['0'] = 'bootstrap.example.org' 1498 with bs_manager.bootstrap_context( 1499 [], omit_config={'bootstrap_host', 'series'}): 1500 pass 1501 ue_mock.assert_called_with(client.env, 'bar', agent_url='url', 1502 agent_stream='devel', region=None) 1503 wfp_mock.assert_called_once_with( 1504 'bootstrap.example.org', 22, timeout=120) 1505 1506 def test_bootstrap_context_sets_has_controller(self): 1507 client = self.make_client() 1508 bs_manager = BootstrapManager( 1509 'foobar', client, client, None, [], None, None, None, None, 1510 None, False) 1511 with patch.object(client, 'kill_controller'): 1512 with bs_manager.bootstrap_context([]): 1513 self.assertIsTrue(bs_manager.has_controller) 1514 self.assertIsTrue(bs_manager.has_controller) 1515 1516 def test_existing_bootstrap_context_sets_has_controller(self): 1517 client = self.make_client() 1518 bs_manager = BootstrapManager( 1519 'foobar', client, client, None, [], None, None, None, None, 1520 None, False) 1521 with patch.object(client, 'kill_controller'): 1522 with bs_manager.existing_bootstrap_context([]): 1523 self.assertIsTrue(bs_manager.has_controller) 1524 self.assertIsTrue(bs_manager.has_controller) 1525 1526 def test_handle_bootstrap_exceptions_ignores_soft_deadline(self): 1527 env = JujuData('foo', {'type': 'nonlocal'}) 1528 client = ModelClient(env, None, None) 1529 tear_down_client = ModelClient(env, None, None) 1530 soft_deadline = datetime(2015, 1, 2, 3, 4, 6) 1531 now = soft_deadline + timedelta(seconds=1) 1532 client.env.juju_home = use_context(self, temp_dir()) 1533 bs_manager = BootstrapManager( 1534 'foobar', client, tear_down_client, None, [], None, None, None, 1535 None, client.env.juju_home, False) 1536 1537 def do_check(*args, **kwargs): 1538 with client.check_timeouts(): 1539 with tear_down_client.check_timeouts(): 1540 return make_fake_juju_return() 1541 1542 with patch.object(bs_manager.tear_down_client, 'juju', 1543 side_effect=do_check, autospec=True): 1544 with patch.object(client._backend, '_now', return_value=now): 1545 fake_exception = Exception() 1546 with self.assertRaises(LoggedException) as exc: 1547 with bs_manager.handle_bootstrap_exceptions(): 1548 client._backend.soft_deadline = soft_deadline 1549 tear_down_client._backend.soft_deadline = soft_deadline 1550 raise fake_exception 1551 self.assertIs(fake_exception, exc.exception.exception) 1552 1553 def test_tear_down_requires_same_env(self): 1554 client = self.make_client() 1555 client.env.juju_home = 'foobar' 1556 tear_down_client = self.make_client() 1557 tear_down_client.env.juju_home = 'barfoo' 1558 bs_manager = BootstrapManager( 1559 'foobar', client, tear_down_client, 1560 None, [], None, None, None, None, client.env.juju_home, False) 1561 with self.assertRaisesRegexp(AssertionError, 1562 'Tear down client needs same env'): 1563 with patch.object(client, 'destroy_controller', 1564 autospec=True) as destroy_mock: 1565 bs_manager.tear_down() 1566 self.assertEqual('barfoo', tear_down_client.env.juju_home) 1567 self.assertIsFalse(destroy_mock.called) 1568 1569 def test_dump_all_multi_model(self): 1570 client = fake_juju_client() 1571 client.bootstrap() 1572 with temp_dir() as log_dir: 1573 bs_manager = BootstrapManager( 1574 'foobar', client, client, 1575 None, [], None, None, None, None, log_dir, False) 1576 with patch('deploy_stack.dump_env_logs_known_hosts') as del_mock: 1577 with patch.object(bs_manager, '_should_dump', 1578 return_value=True): 1579 bs_manager.dump_all_logs() 1580 1581 clients = dict((c[1][0].env.environment, c[1][0]) 1582 for c in del_mock.mock_calls) 1583 self.assertItemsEqual( 1584 [call(client, os.path.join(log_dir, 'name'), None, {}), 1585 call(clients['controller'], os.path.join(log_dir, 'controller'), 1586 'foo/models/cache.yaml', {})], 1587 del_mock.mock_calls) 1588 1589 def test_dump_all_multi_model_iter_failure(self): 1590 client = fake_juju_client() 1591 client.bootstrap() 1592 with temp_dir() as log_dir: 1593 bs_manager = BootstrapManager( 1594 'foobar', client, client, 1595 None, [], None, None, None, None, log_dir, False) 1596 with patch('deploy_stack.dump_env_logs_known_hosts') as del_mock: 1597 with patch.object(client, 'iter_model_clients', 1598 side_effect=Exception): 1599 with patch.object(bs_manager, '_should_dump', 1600 return_value=True): 1601 bs_manager.dump_all_logs() 1602 1603 clients = dict((c[1][0].env.environment, c[1][0]) 1604 for c in del_mock.mock_calls) 1605 1606 self.assertItemsEqual( 1607 [call(client, os.path.join(log_dir, 'name'), None, {}), 1608 call(clients['controller'], os.path.join(log_dir, 'controller'), 1609 'foo/models/cache.yaml', {})], 1610 del_mock.mock_calls) 1611 1612 def test_dump_all_logs_ignores_soft_deadline(self): 1613 1614 def do_check(client, *args, **kwargs): 1615 with client.check_timeouts(): 1616 pass 1617 1618 client = fake_juju_client() 1619 client._backend._past_deadline = True 1620 client.bootstrap() 1621 with temp_dir() as log_dir: 1622 bs_manager = BootstrapManager( 1623 'foobar', client, client, 1624 None, [], None, None, None, None, log_dir, False) 1625 with patch.object(bs_manager, '_should_dump', return_value=True, 1626 autospec=True): 1627 with patch('deploy_stack.dump_env_logs_known_hosts', 1628 side_effect=do_check, autospec=True): 1629 bs_manager.dump_all_logs() 1630 1631 def test_runtime_context_raises_logged_exception(self): 1632 client = fake_juju_client() 1633 client.bootstrap() 1634 bs_manager = BootstrapManager( 1635 'foobar', client, client, 1636 None, [], None, None, None, None, client.env.juju_home, False) 1637 bs_manager.has_controller = True 1638 test_error = Exception("Some exception") 1639 test_error.output = "a stdout value" 1640 test_error.stderr = "a stderr value" 1641 with patch.object(bs_manager, 'dump_all_logs', autospec=True): 1642 with patch('deploy_stack.safe_print_status', 1643 autospec=True) as sp_mock: 1644 with self.assertRaises(LoggedException) as err_ctx: 1645 with bs_manager.runtime_context([]): 1646 raise test_error 1647 self.assertIs(err_ctx.exception.exception, test_error) 1648 self.assertIn("a stdout value", self.log_stream.getvalue()) 1649 self.assertIn("a stderr value", self.log_stream.getvalue()) 1650 sp_mock.assert_called_once_with(client) 1651 1652 def test_runtime_context_raises_logged_exception_no_controller(self): 1653 client = fake_juju_client() 1654 client.bootstrap() 1655 bs_manager = BootstrapManager( 1656 'foobar', client, client, 1657 None, [], None, None, None, None, client.env.juju_home, False) 1658 bs_manager.has_controller = False 1659 test_error = Exception("Some exception") 1660 test_error.output = "a stdout value" 1661 test_error.stderr = "a stderr value" 1662 with patch.object(bs_manager, 'dump_all_logs', autospec=True): 1663 with patch('deploy_stack.safe_print_status', 1664 autospec=True) as sp_mock: 1665 with self.assertRaises(LoggedException) as err_ctx: 1666 with bs_manager.runtime_context([]): 1667 raise test_error 1668 self.assertIs(err_ctx.exception.exception, test_error) 1669 self.assertIn("a stdout value", self.log_stream.getvalue()) 1670 self.assertIn("a stderr value", self.log_stream.getvalue()) 1671 self.assertEqual(0, sp_mock.call_count) 1672 self.assertIn( 1673 "Client lost controller, not calling status", 1674 self.log_stream.getvalue()) 1675 1676 def test_runtime_context_looks_up_host(self): 1677 client = fake_juju_client() 1678 client.bootstrap() 1679 bs_manager = BootstrapManager( 1680 'foobar', client, client, 1681 None, [], None, None, None, None, client.env.juju_home, False) 1682 with patch.object(bs_manager, 'dump_all_logs', autospec=True): 1683 with bs_manager.runtime_context([]): 1684 self.assertEqual({ 1685 '0': '0.example.com'}, bs_manager.known_hosts) 1686 1687 @patch('deploy_stack.dump_env_logs_known_hosts', autospec=True) 1688 def test_runtime_context_addable_machines_no_known_hosts(self, del_mock): 1689 client = fake_juju_client() 1690 client.bootstrap() 1691 bs_manager = BootstrapManager( 1692 'foobar', client, client, 1693 None, [], None, None, None, None, client.env.juju_home, False) 1694 bs_manager.known_hosts = {} 1695 with patch.object(bs_manager.client, 'add_ssh_machines', 1696 autospec=True) as ads_mock: 1697 with patch.object(bs_manager, 'dump_all_logs', autospec=True): 1698 with bs_manager.runtime_context(['baz']): 1699 ads_mock.assert_called_once_with(['baz']) 1700 1701 @patch('deploy_stack.BootstrapManager.dump_all_logs', autospec=True) 1702 def test_runtime_context_addable_machines_with_known_hosts(self, dal_mock): 1703 client = fake_juju_client() 1704 client.bootstrap() 1705 with temp_dir() as log_dir: 1706 bs_manager = BootstrapManager( 1707 'foobar', client, client, 1708 None, [], None, None, None, None, log_dir, False) 1709 bs_manager.known_hosts['0'] = 'example.org' 1710 with patch.object(bs_manager.client, 'add_ssh_machines', 1711 autospec=True) as ads_mock: 1712 with bs_manager.runtime_context(['baz']): 1713 ads_mock.assert_called_once_with(['baz']) 1714 1715 @contextmanager 1716 def no_controller_manager(self): 1717 client = fake_juju_client() 1718 client.bootstrap() 1719 bs_manager = BootstrapManager( 1720 'foobar', client, client, 1721 None, [], None, None, None, None, self.juju_home, False) 1722 bs_manager.has_controller = False 1723 with patch('deploy_stack.safe_print_status', 1724 autospec=True) as sp_mock: 1725 with patch.object( 1726 client, 'juju', wrap=client.juju, 1727 return_value=make_fake_juju_return()) as juju_mock: 1728 with patch.object(client, 'get_juju_output', 1729 wraps=client.get_juju_output) as gjo_mock: 1730 with patch.object(bs_manager, '_should_dump', 1731 return_value=True, autospec=True): 1732 with patch('deploy_stack.get_remote_machines', 1733 return_value={}): 1734 yield bs_manager 1735 self.assertEqual(sp_mock.call_count, 0) 1736 self.assertEqual(0, gjo_mock.call_count) 1737 juju_mock.assert_called_once_with( 1738 'kill-controller', ('name', '-y'), check=True, include_e=False, 1739 timeout=600) 1740 1741 def test_runtime_context_no_controller(self): 1742 with self.no_controller_manager() as bs_manager: 1743 with bs_manager.runtime_context([]): 1744 pass 1745 1746 def test_runtime_context_no_controller_exception(self): 1747 with self.no_controller_manager() as bs_manager: 1748 with self.assertRaises(LoggedException): 1749 with bs_manager.runtime_context([]): 1750 raise ValueError 1751 1752 @contextmanager 1753 def logged_exception_bs_manager(self): 1754 client = fake_juju_client() 1755 with temp_dir() as root: 1756 log_dir = os.path.join(root, 'log-dir') 1757 os.mkdir(log_dir) 1758 bs_manager = BootstrapManager( 1759 'foobar', client, client, 1760 None, [], None, None, None, None, log_dir, False) 1761 juju_home = os.path.join(root, 'juju-home') 1762 os.mkdir(juju_home) 1763 client.env.juju_home = juju_home 1764 yield bs_manager 1765 1766 def test_booted_context_handles_logged_exception(self): 1767 with self.logged_exception_bs_manager() as bs_manager: 1768 with self.assertRaises(SystemExit): 1769 with patch.object(bs_manager, 'dump_all_logs'): 1770 with bs_manager.booted_context(False): 1771 raise LoggedException() 1772 1773 def test_booted_context_raises_logged_exception(self): 1774 with self.logged_exception_bs_manager() as bs_manager: 1775 bs_manager.logged_exception_exit = False 1776 with self.assertRaises(LoggedException): 1777 with patch.object(bs_manager, 'dump_all_logs'): 1778 with bs_manager.booted_context(False): 1779 raise LoggedException() 1780 1781 def test_booted_context_omits_supported(self): 1782 client = fake_juju_client() 1783 client.env.juju_home = use_context(self, temp_dir()) 1784 client.bootstrap_replaces = {'agent-version', 'series', 1785 'bootstrap-host', 'agent-stream'} 1786 ue_mock = use_context( 1787 self, patch('deploy_stack.update_env', wraps=update_env)) 1788 wfp_mock = use_context( 1789 self, patch('deploy_stack.wait_for_port', autospec=True)) 1790 bs_manager = BootstrapManager( 1791 'bar', client, client, 'bootstrap.example.org', 1792 [], 'wacky', 'url', 'devel', None, client.env.juju_home, False) 1793 with patch.object(bs_manager, 'runtime_context'): 1794 with bs_manager.booted_context([]): 1795 pass 1796 self.assertEqual({ 1797 'name': 'bar', 1798 'default-series': 'wacky', 1799 'agent-metadata-url': 'url', 1800 'type': 'foo', 1801 'region': 'bar', 1802 'test-mode': True, 1803 }, client.get_model_config()) 1804 ue_mock.assert_called_with(client.env, 'bar', agent_url='url', 1805 region=None) 1806 wfp_mock.assert_called_once_with( 1807 'bootstrap.example.org', 22, timeout=120) 1808 1809 @contextmanager 1810 def booted_to_bootstrap(self, bs_manager): 1811 """Preform patches to focus on the call to bootstrap.""" 1812 with patch.object(bs_manager, 'dump_all_logs'): 1813 with patch.object(bs_manager, 'runtime_context'): 1814 with patch.object( 1815 bs_manager.client, 'juju', 1816 return_value=make_fake_juju_return()): 1817 with patch.object(bs_manager.client, 'bootstrap') as mock: 1818 yield mock 1819 1820 def test_booted_context_kwargs(self): 1821 client = fake_juju_client() 1822 with temp_dir() as root: 1823 log_dir = os.path.join(root, 'log-dir') 1824 os.mkdir(log_dir) 1825 bs_manager = BootstrapManager( 1826 'foobar', client, client, 1827 None, [], None, None, None, None, log_dir, False) 1828 juju_home = os.path.join(root, 'juju-home') 1829 os.mkdir(juju_home) 1830 client.env.juju_home = juju_home 1831 with self.booted_to_bootstrap(bs_manager) as bootstrap_mock: 1832 with bs_manager.booted_context(False, to='test'): 1833 bootstrap_mock.assert_called_once_with( 1834 upload_tools=False, to='test', bootstrap_series=None) 1835 with self.booted_to_bootstrap(bs_manager) as bootstrap_mock: 1836 with bs_manager.existing_booted_context(False, to='test'): 1837 bootstrap_mock.assert_called_once_with( 1838 upload_tools=False, to='test', bootstrap_series=None) 1839 1840 def test_runtime_context_teardown_ignores_soft_deadline(self): 1841 env = JujuData('foo', {'type': 'nonlocal'}) 1842 soft_deadline = datetime(2015, 1, 2, 3, 4, 6) 1843 now = soft_deadline + timedelta(seconds=1) 1844 client = ModelClient(env, None, None) 1845 tear_down_client = ModelClient(env, None, None) 1846 1847 def do_check_client(*args, **kwargs): 1848 with client.check_timeouts(): 1849 return iter([]) 1850 1851 def do_check_teardown_client(*args, **kwargs): 1852 with tear_down_client.check_timeouts(): 1853 return iter([]) 1854 1855 with temp_dir() as log_dir: 1856 bs_manager = BootstrapManager( 1857 'foobar', client, tear_down_client, 1858 None, [], None, None, None, None, log_dir, False) 1859 bs_manager.known_hosts['0'] = 'example.org' 1860 with patch.object(bs_manager.client, 'juju', 1861 side_effect=do_check_client, autospec=True): 1862 with patch.object(bs_manager.client, 'iter_model_clients', 1863 side_effect=do_check_client, autospec=True, 1864 ): 1865 with patch.object(bs_manager, 'tear_down', 1866 do_check_teardown_client): 1867 with patch.object(client._backend, '_now', 1868 return_value=now): 1869 with bs_manager.runtime_context(['baz']): 1870 client._backend.soft_deadline = soft_deadline 1871 td_backend = tear_down_client._backend 1872 td_backend.soft_deadline = soft_deadline 1873 1874 @contextmanager 1875 def make_bootstrap_manager(self): 1876 client = fake_juju_client() 1877 with temp_dir() as log_dir: 1878 bs_manager = BootstrapManager( 1879 'foobar', client, client, 1880 None, [], None, None, None, None, log_dir, False) 1881 yield bs_manager 1882 1883 def test_top_context_dumps_timings(self): 1884 with self.make_bootstrap_manager() as bs_manager: 1885 with patch('deploy_stack.dump_juju_timings') as djt_mock: 1886 with bs_manager.top_context(): 1887 pass 1888 djt_mock.assert_called_once_with(bs_manager.client, bs_manager.log_dir) 1889 1890 def test_top_context_dumps_timings_on_exception(self): 1891 with self.make_bootstrap_manager() as bs_manager: 1892 with patch('deploy_stack.dump_juju_timings') as djt_mock: 1893 with self.assertRaises(ValueError): 1894 with bs_manager.top_context(): 1895 raise ValueError 1896 djt_mock.assert_called_once_with(bs_manager.client, bs_manager.log_dir) 1897 1898 def test_top_context_no_log_dir_skips_timings(self): 1899 with self.make_bootstrap_manager() as bs_manager: 1900 bs_manager.log_dir = None 1901 with patch('deploy_stack.dump_juju_timings') as djt_mock: 1902 with bs_manager.top_context(): 1903 pass 1904 self.assertEqual(djt_mock.call_count, 0) 1905 1906 def test_collect_resource_details_collects_expected_details(self): 1907 controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202' 1908 members = [ 1909 Machine('0', {'dns-name': '10.0.0.0', 1910 'instance-id': 'juju-aaaa-machine-0'}), 1911 Machine('1', {'dns-name': '10.0.0.1', 1912 'instance-id': 'juju-dddd-machine-1'}), 1913 ] 1914 client = fake_juju_client() 1915 bs_manager = BootstrapManager( 1916 'foobar', client, client, None, [], None, None, None, None, 1917 client.env.juju_home, False) 1918 result = { 1919 'controller-uuid': controller_uuid, 1920 'instances': [(m.info['instance-id'], m.info['dns-name']) 1921 for m in members] 1922 } 1923 1924 with patch.object(client, 'get_controller_uuid', autospec=True, 1925 return_value=controller_uuid): 1926 with patch.object(client, 'get_controller_members', autospec=True, 1927 return_value=members): 1928 bs_manager.collect_resource_details() 1929 self.assertEqual(bs_manager.resource_details, result) 1930 1931 def test_ensure_cleanup(self): 1932 client = fake_juju_client() 1933 with temp_dir() as root: 1934 log_dir = os.path.join(root, 'log-dir') 1935 os.mkdir(log_dir) 1936 bs_manager = BootstrapManager( 1937 'controller', client, client, 1938 None, [], None, None, None, None, log_dir, False) 1939 mock_substrate = Mock() 1940 mock_details = {} 1941 with patch('deploy_stack.make_substrate_manager', autospec=True)\ 1942 as msm: 1943 msm.return_value.__enter__.return_value = mock_substrate 1944 bs_manager.resource_details = mock_details 1945 bs_manager.ensure_cleanup() 1946 mock_substrate.ensure_cleanup.assert_called_once_with( 1947 mock_details) 1948 msm.assert_called_once_with(client.env) 1949 1950 def test_ensure_cleanup_resource_details_empty(self): 1951 client = fake_juju_client() 1952 with temp_dir() as root: 1953 log_dir = os.path.join(root, 'log-dir') 1954 os.mkdir(log_dir) 1955 bs_manager = BootstrapManager( 1956 'controller', client, client, 1957 None, [], None, None, None, None, log_dir, False) 1958 with patch('deploy_stack.make_substrate_manager', autospec=True) \ 1959 as msm: 1960 rl = bs_manager.ensure_cleanup() 1961 self.assertEquals(0, msm.call_count) 1962 self.assertEquals(rl, []) 1963 1964 def test_ensure_cleanup_substrate_none(self): 1965 client = fake_juju_client() 1966 with temp_dir() as root: 1967 log_dir = os.path.join(root, 'log-dir') 1968 os.mkdir(log_dir) 1969 bs_manager = BootstrapManager( 1970 'controller', client, client, 1971 None, [], None, None, None, None, log_dir, False) 1972 mock_details = {} 1973 bs_manager.resource_details = mock_details 1974 with patch('deploy_stack.make_substrate_manager', autospec=True)\ 1975 as msm: 1976 msm.return_value.__enter__.return_value = None 1977 rl = bs_manager.ensure_cleanup() 1978 self.assertIn("foo is an unknown provider." 1979 " Unable to ensure cleanup", 1980 self.log_stream.getvalue()) 1981 self.assertEquals(rl, []) 1982 1983 1984 class TestBootContext(FakeHomeTestCase): 1985 1986 def setUp(self): 1987 super(TestBootContext, self).setUp() 1988 self.addContext(patch('sys.stdout')) 1989 1990 @contextmanager 1991 def bc_context(self, client, log_dir=None, keep_env=False): 1992 dal_mock = self.addContext( 1993 patch('deploy_stack.BootstrapManager.dump_all_logs')) 1994 self.addContext(patch('deploy_stack.get_machine_dns_name', 1995 return_value='foo', autospec=True)) 1996 self.addContext(patch( 1997 'deploy_stack.BootstrapManager.collect_resource_details', 1998 autospec=True)) 1999 models = [{'name': 'controller'}, {'name': 'bar'}] 2000 self.addContext(patch.object(client, '_get_models', 2001 return_value=models, autospec=True)) 2002 po_count = 0 2003 with patch('subprocess.Popen', autospec=True, 2004 return_value=FakePopen( 2005 'kill-controller', '', 0)) as po_mock: 2006 with patch('deploy_stack.BootstrapManager.tear_down', 2007 autospec=True) as tear_down_mock: 2008 with patch.object(client, 'kill_controller', 2009 autospec=True) as kill_mock: 2010 yield 2011 self.assertEqual(po_count, po_mock.call_count) 2012 dal_mock.assert_called_once_with() 2013 tear_down_count = 0 if keep_env else 1 2014 self.assertEqual(1, kill_mock.call_count) 2015 self.assertEqual(tear_down_count, tear_down_mock.call_count) 2016 2017 def test_bootstrap_context(self): 2018 cc_mock = self.addContext(patch('subprocess.check_call')) 2019 client = ModelClient(JujuData( 2020 'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path') 2021 with self.bc_context(client, 'log_dir'): 2022 with observable_temp_file() as config_file: 2023 with boot_context('bar', client, None, [], None, None, None, 2024 'log_dir', keep_env=False, 2025 upload_tools=False): 2026 pass 2027 assert_juju_call(self, cc_mock, client, ( 2028 'path', '--show-log', 'bootstrap', '--constraints', 2029 'mem=2G', 'paas/qux', 'bar', '--config', config_file.name, 2030 '--default-model', 'bar', '--agent-version', '1.23'), 0) 2031 assert_juju_call(self, cc_mock, client, ( 2032 'path', '--show-log', 'list-controllers'), 1) 2033 assert_juju_call(self, cc_mock, client, ( 2034 'path', '--show-log', 'list-models', '-c', 'bar'), 2) 2035 assert_juju_call(self, cc_mock, client, ( 2036 'path', '--show-log', 'show-status', '-m', 'bar:controller', 2037 '--format', 'yaml'), 3) 2038 assert_juju_call(self, cc_mock, client, ( 2039 'path', '--show-log', 'show-status', '-m', 'bar:bar', 2040 '--format', 'yaml'), 4) 2041 2042 def test_keep_env(self): 2043 cc_mock = self.addContext(patch('subprocess.check_call')) 2044 client = ModelClient(JujuData( 2045 'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path') 2046 with self.bc_context(client, keep_env=True): 2047 with observable_temp_file() as config_file: 2048 with boot_context('bar', client, None, [], None, None, None, 2049 None, keep_env=True, upload_tools=False): 2050 pass 2051 assert_juju_call(self, cc_mock, client, ( 2052 'path', '--show-log', 'bootstrap', '--constraints', 2053 'mem=2G', 'paas/qux', 'bar', '--config', config_file.name, 2054 '--default-model', 'bar', '--agent-version', '1.23'), 0) 2055 assert_juju_call(self, cc_mock, client, ( 2056 'path', '--show-log', 'list-controllers'), 1) 2057 assert_juju_call(self, cc_mock, client, ( 2058 'path', '--show-log', 'list-models', '-c', 'bar'), 2) 2059 assert_juju_call(self, cc_mock, client, ( 2060 'path', '--show-log', 'show-status', '-m', 'bar:controller', 2061 '--format', 'yaml'), 3) 2062 assert_juju_call(self, cc_mock, client, ( 2063 'path', '--show-log', 'show-status', '-m', 'bar:bar', 2064 '--format', 'yaml'), 4) 2065 2066 def test_upload_tools(self): 2067 cc_mock = self.addContext(patch('subprocess.check_call')) 2068 client = ModelClient(JujuData( 2069 'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path') 2070 with self.bc_context(client): 2071 with observable_temp_file() as config_file: 2072 with boot_context('bar', client, None, [], None, None, None, 2073 None, keep_env=False, upload_tools=True): 2074 pass 2075 assert_juju_call(self, cc_mock, client, ( 2076 'path', '--show-log', 'bootstrap', '--upload-tools', 2077 '--constraints', 'mem=2G', 'paas/qux', 'bar', '--config', 2078 config_file.name, '--default-model', 'bar'), 0) 2079 2080 def test_calls_update_env(self): 2081 cc_mock = self.addContext(patch('subprocess.check_call')) 2082 client = ModelClient(JujuData( 2083 'foo', {'type': 'paas', 'region': 'qux'}), '2.3', 'path') 2084 ue_mock = self.addContext( 2085 patch('deploy_stack.update_env', wraps=update_env)) 2086 with self.bc_context(client): 2087 with observable_temp_file() as config_file: 2088 with boot_context('bar', client, None, [], 'wacky', 'url', 2089 'devel', None, keep_env=False, 2090 upload_tools=False): 2091 pass 2092 ue_mock.assert_called_with( 2093 client.env, 'bar', agent_url='url', agent_stream='devel', 2094 series='wacky', bootstrap_host=None, region=None) 2095 assert_juju_call(self, cc_mock, client, ( 2096 'path', '--show-log', 'bootstrap', '--constraints', 'mem=2G', 2097 'paas/qux', 'bar', '--config', config_file.name, 2098 '--default-model', 'bar', '--agent-version', '2.3', 2099 '--bootstrap-series', 'wacky'), 0) 2100 2101 def test_with_bootstrap_failure(self): 2102 2103 class FakeException(Exception): 2104 """A sentry exception to be raised by bootstrap.""" 2105 2106 client = ModelClient(JujuData( 2107 'foo', {'type': 'paas'}), '1.23', 'path') 2108 self.addContext(patch('deploy_stack.get_machine_dns_name', 2109 return_value='foo')) 2110 self.addContext(patch('subprocess.check_call')) 2111 tear_down_mock = self.addContext( 2112 patch('deploy_stack.BootstrapManager.tear_down', autospec=True)) 2113 kill_mock = self.addContext( 2114 patch('jujupy.ModelClient.kill_controller', autospec=True)) 2115 po_mock = self.addContext(patch( 2116 'subprocess.Popen', autospec=True, 2117 return_value=FakePopen('kill-controller', '', 0))) 2118 self.addContext(patch('deploy_stack.wait_for_port')) 2119 fake_exception = FakeException() 2120 self.addContext(patch.object(client, 'bootstrap', 2121 side_effect=fake_exception)) 2122 crl_mock = self.addContext(patch('deploy_stack.copy_remote_logs')) 2123 al_mock = self.addContext(patch('deploy_stack.archive_logs')) 2124 le_mock = self.addContext(patch('logging.exception')) 2125 with self.assertRaises(SystemExit): 2126 with boot_context('bar', client, 'baz', [], None, None, None, 2127 'log_dir', keep_env=False, upload_tools=True): 2128 pass 2129 le_mock.assert_called_once_with(fake_exception) 2130 self.assertEqual(crl_mock.call_count, 1) 2131 call_args = crl_mock.call_args[0] 2132 self.assertIsInstance(call_args[0], _Remote) 2133 self.assertEqual(call_args[0].get_address(), 'baz') 2134 self.assertEqual(call_args[1], 'log_dir') 2135 al_mock.assert_called_once_with('log_dir') 2136 self.assertEqual(0, tear_down_mock.call_count) 2137 self.assertEqual(2, kill_mock.call_count) 2138 self.assertEqual(0, po_mock.call_count) 2139 2140 def test_region(self): 2141 self.addContext(patch('subprocess.check_call', autospec=True)) 2142 client = ModelClient(JujuData( 2143 'foo', {'type': 'paas'}), '1.23', 'path') 2144 with self.bc_context(client, 'log_dir'): 2145 with boot_context('bar', client, None, [], None, None, None, 2146 'log_dir', keep_env=False, upload_tools=False, 2147 region='steve'): 2148 pass 2149 self.assertEqual('steve', client.env.get_region()) 2150 2151 def test_status_error_raises(self): 2152 """An error on final show-status propagates so an assess will fail.""" 2153 error = subprocess.CalledProcessError(1, ['juju'], '') 2154 effects = [None, None, None, None, None, None, error] 2155 cc_mock = self.addContext(patch('subprocess.check_call', autospec=True, 2156 side_effect=effects)) 2157 client = ModelClient(JujuData( 2158 'foo', {'type': 'paas', 'region': 'qux'}), '1.23', 'path') 2159 with self.bc_context(client, 'log_dir'): 2160 with observable_temp_file() as config_file: 2161 with self.assertRaises(subprocess.CalledProcessError) as ctx: 2162 with boot_context('bar', client, None, [], None, None, 2163 None, 'log_dir', keep_env=False, 2164 upload_tools=False): 2165 pass 2166 self.assertIs(ctx.exception, error) 2167 assert_juju_call(self, cc_mock, client, ( 2168 'path', '--show-log', 'bootstrap', '--constraints', 2169 'mem=2G', 'paas/qux', 'bar', '--config', config_file.name, 2170 '--default-model', 'bar', '--agent-version', '1.23'), 0) 2171 assert_juju_call(self, cc_mock, client, ( 2172 'path', '--show-log', 'list-controllers'), 1) 2173 assert_juju_call(self, cc_mock, client, ( 2174 'path', '--show-log', 'list-models', '-c', 'bar'), 2) 2175 assert_juju_call(self, cc_mock, client, ( 2176 'path', '--show-log', 'show-status', '-m', 'bar:controller', 2177 '--format', 'yaml'), 3) 2178 assert_juju_call(self, cc_mock, client, ( 2179 'path', '--show-log', 'show-status', '-m', 'bar:bar', 2180 '--format', 'yaml'), 4) 2181 2182 2183 class TestDeployJobParseArgs(FakeHomeTestCase): 2184 2185 def test_deploy_job_parse_args(self): 2186 args = deploy_job_parse_args(['foo', 'bar/juju', 'baz', 'qux']) 2187 self.assertEqual(args, Namespace( 2188 agent_stream=None, 2189 agent_url=None, 2190 bootstrap_host=None, 2191 debug=False, 2192 env='foo', 2193 temp_env_name='qux', 2194 keep_env=False, 2195 logs='baz', 2196 machine=[], 2197 juju_bin='bar/juju', 2198 series=None, 2199 upgrade=False, 2200 verbose=logging.INFO, 2201 upload_tools=False, 2202 region=None, 2203 to=None, 2204 deadline=None, 2205 controller_host=None, 2206 use_charmstore=False, 2207 )) 2208 2209 def test_upload_tools(self): 2210 args = deploy_job_parse_args( 2211 ['foo', 'bar/juju', 'baz', 'qux', '--upload-tools']) 2212 self.assertEqual(args.upload_tools, True) 2213 2214 def test_agent_stream(self): 2215 args = deploy_job_parse_args( 2216 ['foo', 'bar/juju', 'baz', 'qux', '--agent-stream', 'wacky']) 2217 self.assertEqual('wacky', args.agent_stream) 2218 2219 def test_use_charmstore(self): 2220 args = deploy_job_parse_args( 2221 ['foo', 'bar/juju', 'baz', 'qux', '--use-charmstore']) 2222 self.assertIs(args.use_charmstore, True) 2223 2224 2225 class TestWaitForStateServerToShutdown(FakeHomeTestCase): 2226 2227 def test_openstack(self): 2228 env = JujuData('foo', { 2229 'type': 'openstack', 2230 'region': 'lcy05', 2231 'username': 'steve', 2232 'password': 'password1', 2233 'tenant-name': 'steven', 2234 'auth-url': 'http://example.org', 2235 }, self.juju_home) 2236 client = fake_juju_client(env=env) 2237 with patch('deploy_stack.wait_for_port', autospec=True) as wfp_mock: 2238 with patch('deploy_stack.has_nova_instance', autospec=True, 2239 return_value=False) as hni_mock: 2240 with patch('deploy_stack.print_now', autospec=True) as pn_mock: 2241 wait_for_state_server_to_shutdown( 2242 'example.org', client, 'i-255') 2243 self.assertEqual(pn_mock.mock_calls, [ 2244 call('Waiting for port to close on example.org'), 2245 call('Closed.'), 2246 call('i-255 was removed from nova list'), 2247 ]) 2248 wfp_mock.assert_called_once_with('example.org', 17070, closed=True, 2249 timeout=60) 2250 hni_mock.assert_called_once_with(client.env, 'i-255') 2251 2252 2253 class TestErrorIfUnclean(FakeHomeTestCase): 2254 def test_empty_unclean_resources(self): 2255 uncleaned_resources = [] 2256 error_if_unclean(uncleaned_resources) 2257 self.assertEquals(self.log_stream.getvalue(), '') 2258 2259 def test_contain_unclean_resources(self): 2260 uncleaned_resources = [ 2261 { 2262 'resource': 'instances', 2263 'errors': [('ifoo', 'err-msg'), ('ibar', 'err-msg')] 2264 }, 2265 { 2266 'resource': 'security groups', 2267 'errors': [('sg-bar', 'err-msg')] 2268 } 2269 ] 2270 error_if_unclean(uncleaned_resources) 2271 self.assertListEqual(self.log_stream.getvalue().splitlines(), [ 2272 "CRITICAL Following resource requires manual cleanup", 2273 "CRITICAL instances", 2274 "CRITICAL \tifoo: err-msg", 2275 "CRITICAL \tibar: err-msg", 2276 "CRITICAL security groups", 2277 "CRITICAL \tsg-bar: err-msg" 2278 ]) 2279 2280 def test_unclean_resources_without_sg_error(self): 2281 uncleaned_resources = [ 2282 { 2283 'resource': 'instances', 2284 'errors': [('ifoo', 'err-msg'), ('ibar', 'err-msg')] 2285 }, 2286 ] 2287 error_if_unclean(uncleaned_resources) 2288 self.assertListEqual(self.log_stream.getvalue().splitlines(), [ 2289 "CRITICAL Following resource requires manual cleanup", 2290 "CRITICAL instances", 2291 "CRITICAL \tifoo: err-msg", 2292 "CRITICAL \tibar: err-msg", 2293 ]) 2294 2295 def test_unclean_resources_without_instances_error(self): 2296 uncleaned_resources = [ 2297 { 2298 'resource': 'security groups', 2299 'errors': [('sg-bar', 'err-msg')] 2300 } 2301 ] 2302 error_if_unclean(uncleaned_resources) 2303 self.assertListEqual(self.log_stream.getvalue().splitlines(), [ 2304 "CRITICAL Following resource requires manual cleanup", 2305 "CRITICAL security groups", 2306 "CRITICAL \tsg-bar: err-msg" 2307 ])