github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/jujupy/tests/test_client.py (about) 1 from contextlib import contextmanager 2 import copy 3 from datetime import ( 4 datetime, 5 timedelta, 6 ) 7 import json 8 import logging 9 import os 10 import socket 11 try: 12 from StringIO import StringIO 13 except ImportError: 14 from io import StringIO 15 import subprocess 16 import sys 17 from textwrap import dedent 18 19 try: 20 from mock import ( 21 call, 22 Mock, 23 patch, 24 ) 25 except ImportError: 26 from unittest.mock import ( 27 call, 28 Mock, 29 patch, 30 ) 31 import yaml 32 33 from jujupy.configuration import ( 34 get_bootstrap_config_path, 35 ) 36 from jujupy import ( 37 fake_juju_client, 38 ) 39 from jujupy.exceptions import ( 40 AuthNotAccepted, 41 AppError, 42 CannotConnectEnv, 43 ErroredUnit, 44 InvalidEndpoint, 45 MachineError, 46 NameNotAccepted, 47 SoftDeadlineExceeded, 48 StatusError, 49 StatusNotMet, 50 StatusTimeout, 51 TypeNotAccepted, 52 UnitError, 53 ) 54 from jujupy.client import ( 55 Controller, 56 describe_substrate, 57 GroupReporter, 58 get_cache_path, 59 get_machine_dns_name, 60 get_stripped_version_number, 61 get_version_string_parts, 62 juju_home_path, 63 JujuData, 64 Machine, 65 make_safe_config, 66 ModelClient, 67 parse_new_state_server_from_error, 68 temp_bootstrap_env, 69 temp_yaml_file, 70 ) 71 from jujupy.fake import ( 72 get_user_register_command_info, 73 get_user_register_token, 74 ) 75 from jujupy.status import ( 76 Status, 77 StatusItem, 78 ) 79 from jujupy.wait_condition import ( 80 CommandTime, 81 ConditionList, 82 WaitMachineNotPresent, 83 ) 84 from tests import ( 85 assert_juju_call, 86 client_past_deadline, 87 make_fake_juju_return, 88 FakeHomeTestCase, 89 FakePopen, 90 observable_temp_file, 91 patch_juju_call, 92 TestCase, 93 ) 94 from jujupy.utility import ( 95 get_timeout_path, 96 JujuResourceTimeout, 97 scoped_environ, 98 temp_dir, 99 ) 100 101 102 __metaclass__ = type 103 104 105 class TestVersionStringHelpers(TestCase): 106 107 def test_parts_handles_non_tagged_releases(self): 108 self.assertEqual( 109 ('2.0.5', 'alpha', 'arch'), 110 get_version_string_parts('2.0.5-alpha-arch') 111 ) 112 113 def test_parts_handles_tagged_releases(self): 114 self.assertEqual( 115 ('2.0-beta1', 'alpha', 'arch'), 116 get_version_string_parts('2.0-beta1-alpha-arch') 117 ) 118 119 def test_stripped_version_handles_only_version(self): 120 self.assertEqual( 121 'a', 122 get_stripped_version_number('a') 123 ) 124 125 def test_stripped_version_returns_only_version(self): 126 self.assertEqual( 127 'a', 128 get_stripped_version_number('a-b-c') 129 ) 130 131 132 class TestErroredUnit(TestCase): 133 134 def test_output(self): 135 e = ErroredUnit('bar', 'baz') 136 self.assertEqual('bar is in state baz', str(e)) 137 138 139 class ClientTest(FakeHomeTestCase): 140 141 def setUp(self): 142 super(ClientTest, self).setUp() 143 patcher = patch('jujupy.client.pause') 144 backend_patcher = patch('jujupy.backend.pause') 145 self.addCleanup(patcher.stop) 146 self.addCleanup(backend_patcher.stop) 147 self.pause_mock = patcher.start() 148 backend_patcher.start() 149 150 151 class TestTempYamlFile(TestCase): 152 153 def test_temp_yaml_file(self): 154 with temp_yaml_file({'foo': 'bar'}) as yaml_file: 155 with open(yaml_file) as f: 156 self.assertEqual({'foo': 'bar'}, yaml.safe_load(f)) 157 158 159 def backend_call(client, cmd, args, model=None, check=True, timeout=None, 160 extra_env=None): 161 """Return the mock.call for this command.""" 162 return call(cmd, args, client.used_feature_flags, 163 client.env.juju_home, model, check, timeout, extra_env, 164 suppress_err=False) 165 166 167 def make_resource_list(service_app_id='applicationId'): 168 return {'resources': [{ 169 'expected': { 170 'origin': 'upload', 'used': True, 'description': 'foo resource.', 171 'username': 'admin', 'resourceid': 'dummy-resource/foo', 172 'name': 'foo', service_app_id: 'dummy-resource', 'size': 27, 173 'fingerprint': '1234', 'type': 'file', 'path': 'foo.txt'}, 174 'unit': { 175 'origin': 'upload', 'username': 'admin', 'used': True, 176 'name': 'foo', 'resourceid': 'dummy-resource/foo', 177 service_app_id: 'dummy-resource', 'fingerprint': '1234', 178 'path': 'foo.txt', 'size': 27, 'type': 'file', 179 'description': 'foo resource.'}}]} 180 181 182 class TestModelClient(ClientTest): 183 184 def test_get_full_path(self): 185 with patch('subprocess.check_output', 186 return_value=b'asdf\n') as co_mock: 187 with patch('sys.platform', 'linux2'): 188 path = ModelClient.get_full_path() 189 co_mock.assert_called_once_with(('which', 'juju')) 190 expected = u'asdf' 191 self.assertEqual(expected, path) 192 self.assertIs(type(expected), type(path)) 193 194 def test_get_full_path_encoding(self): 195 # Test with non-ascii-compatible encoding 196 with patch('subprocess.check_output', 197 return_value='asdf\n'.encode('EBCDIC-CP-BE')) as co_mock: 198 with patch('sys.platform', 'linux2'): 199 with patch('jujupy.client.getpreferredencoding', 200 return_value='EBCDIC-CP-BE'): 201 path = ModelClient.get_full_path() 202 co_mock.assert_called_once_with(('which', 'juju')) 203 expected = u'asdf' 204 self.assertEqual(expected, path) 205 self.assertIs(type(expected), type(path)) 206 207 def test_no_duplicate_env(self): 208 env = JujuData('foo', {}) 209 client = ModelClient(env, '1.25', 'full_path') 210 self.assertIs(env, client.env) 211 212 def test_get_version(self): 213 value = ' 5.6 \n'.encode('ascii') 214 with patch('subprocess.check_output', return_value=value) as vsn: 215 version = ModelClient.get_version() 216 self.assertEqual('5.6', version) 217 vsn.assert_called_with(('juju', '--version')) 218 219 def test_get_version_path(self): 220 with patch('subprocess.check_output', 221 return_value=' 4.3'.encode('ascii')) as vsn: 222 ModelClient.get_version('foo/bar/baz') 223 vsn.assert_called_once_with(('foo/bar/baz', '--version')) 224 225 def test_get_matching_agent_version(self): 226 client = ModelClient( 227 JujuData(None, {'type': 'lxd'}, juju_home='foo'), 228 '1.23-series-arch', None) 229 self.assertEqual('1.23', client.get_matching_agent_version()) 230 231 def test_upgrade_juju_nonlocal(self): 232 client = ModelClient( 233 JujuData('foo', {'type': 'nonlocal'}), '2.0-betaX', None) 234 with patch.object(client, '_upgrade_juju') as juju_mock: 235 client.upgrade_juju() 236 juju_mock.assert_called_with(('--agent-version', '2.0')) 237 238 def test_upgrade_juju_no_force_version(self): 239 client = ModelClient( 240 JujuData('foo', {'type': 'lxd'}), '2.0-betaX', None) 241 with patch.object(client, '_upgrade_juju') as juju_mock: 242 client.upgrade_juju(force_version=False) 243 juju_mock.assert_called_with(()) 244 245 def test_clone_unchanged(self): 246 client1 = ModelClient(JujuData('foo'), '1.27', 'full/path', 247 debug=True) 248 client2 = client1.clone() 249 self.assertIsNot(client1, client2) 250 self.assertIs(type(client1), type(client2)) 251 self.assertIs(client1.env, client2.env) 252 self.assertEqual(client1.version, client2.version) 253 self.assertEqual(client1.full_path, client2.full_path) 254 self.assertIs(client1.debug, client2.debug) 255 self.assertEqual(client1.feature_flags, client2.feature_flags) 256 self.assertEqual(client1._backend, client2._backend) 257 258 def test_get_cache_path(self): 259 client = ModelClient(JujuData('foo', juju_home='/foo/'), 260 '1.27', 'full/path', debug=True) 261 self.assertEqual('/foo/models/cache.yaml', 262 client.get_cache_path()) 263 264 def test_make_model_config_prefers_agent_metadata_url(self): 265 env = JujuData('qux', { 266 'agent-metadata-url': 'foo', 267 'tools-metadata-url': 'bar', 268 'type': 'baz', 269 }) 270 client = ModelClient(env, None, 'my/juju/bin') 271 self.assertEqual({ 272 'agent-metadata-url': 'foo', 273 'test-mode': True, 274 }, client.make_model_config()) 275 276 def test__bootstrap_config(self): 277 env = JujuData('foo', { 278 'access-key': 'foo', 279 'admin-secret': 'foo', 280 'agent-stream': 'foo', 281 'application-id': 'foo', 282 'application-password': 'foo', 283 'auth-url': 'foo', 284 'authorized-keys': 'foo', 285 'availability-sets-enabled': 'foo', 286 'bootstrap-host': 'foo', 287 'bootstrap-timeout': 'foo', 288 'bootstrap-user': 'foo', 289 'client-email': 'foo', 290 'client-id': 'foo', 291 'container': 'foo', 292 'control-bucket': 'foo', 293 'default-series': 'foo', 294 'development': False, 295 'enable-os-upgrade': 'foo', 296 'host': 'foo', 297 'image-metadata-url': 'foo', 298 'location': 'foo', 299 'maas-oauth': 'foo', 300 'maas-server': 'foo', 301 'manta-key-id': 'foo', 302 'manta-user': 'foo', 303 'management-subscription-id': 'foo', 304 'management-certificate': 'foo', 305 'name': 'foo', 306 'password': 'foo', 307 'prefer-ipv6': 'foo', 308 'private-key': 'foo', 309 'region': 'foo', 310 'sdc-key-id': 'foo', 311 'sdc-url': 'foo', 312 'sdc-user': 'foo', 313 'secret-key': 'foo', 314 'storage-account-name': 'foo', 315 'subscription-id': 'foo', 316 'tenant-id': 'foo', 317 'tenant-name': 'foo', 318 'test-mode': False, 319 'tools-metadata-url': 'steve', 320 'type': 'foo', 321 'username': 'foo', 322 }, 'home') 323 client = ModelClient(env, None, 'my/juju/bin') 324 with client._bootstrap_config() as config_filename: 325 with open(config_filename) as f: 326 self.assertEqual({ 327 'agent-metadata-url': 'steve', 328 'agent-stream': 'foo', 329 'authorized-keys': 'foo', 330 'availability-sets-enabled': 'foo', 331 'bootstrap-timeout': 'foo', 332 'bootstrap-user': 'foo', 333 'container': 'foo', 334 'default-series': 'foo', 335 'development': False, 336 'enable-os-upgrade': 'foo', 337 'image-metadata-url': 'foo', 338 'prefer-ipv6': 'foo', 339 'test-mode': True, 340 }, yaml.safe_load(f)) 341 342 def test_get_cloud_region(self): 343 self.assertEqual( 344 'foo/bar', ModelClient.get_cloud_region('foo', 'bar')) 345 self.assertEqual( 346 'foo', ModelClient.get_cloud_region('foo', None)) 347 348 def test_bootstrap_maas(self): 349 env = JujuData('maas', {'type': 'foo', 'region': 'asdf'}) 350 with patch_juju_call(ModelClient) as mock: 351 client = ModelClient(env, '2.0-zeta1', None) 352 with patch.object(client.env, 'maas', lambda: True): 353 with observable_temp_file() as config_file: 354 client.bootstrap() 355 mock.assert_called_with( 356 'bootstrap', ( 357 '--constraints', 'mem=2G spaces=^endpoint-bindings-data,' 358 '^endpoint-bindings-public', 359 'foo/asdf', 'maas', 360 '--config', config_file.name, '--default-model', 'maas', 361 '--agent-version', '2.0'), 362 include_e=False) 363 364 def test_bootstrap_maas_spaceless(self): 365 # Disable space constraint with environment variable 366 os.environ['JUJU_CI_SPACELESSNESS'] = "1" 367 env = JujuData('maas', {'type': 'foo', 'region': 'asdf'}) 368 with patch_juju_call(ModelClient) as mock: 369 client = ModelClient(env, '2.0-zeta1', None) 370 with patch.object(client.env, 'maas', lambda: True): 371 with observable_temp_file() as config_file: 372 client.bootstrap() 373 mock.assert_called_with( 374 'bootstrap', ( 375 '--constraints', 'mem=2G', 376 'foo/asdf', 'maas', 377 '--config', config_file.name, '--default-model', 'maas', 378 '--agent-version', '2.0'), 379 include_e=False) 380 381 def test_bootstrap_joyent(self): 382 env = JujuData('joyent', { 383 'type': 'joyent', 'sdc-url': 'https://foo.api.joyentcloud.com'}) 384 client = ModelClient(env, '2.0-zeta1', None) 385 with patch_juju_call(client) as mock: 386 with patch.object(client.env, 'joyent', lambda: True): 387 with observable_temp_file() as config_file: 388 client.bootstrap() 389 mock.assert_called_once_with( 390 'bootstrap', ( 391 '--constraints', 'mem=2G cpu-cores=1', 392 'joyent/foo', 'joyent', 393 '--config', config_file.name, 394 '--default-model', 'joyent', '--agent-version', '2.0', 395 ), include_e=False) 396 397 def test_bootstrap(self): 398 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 399 with observable_temp_file() as config_file: 400 with patch_juju_call(ModelClient) as mock: 401 client = ModelClient(env, '2.0-zeta1', None) 402 client.bootstrap() 403 mock.assert_called_with( 404 'bootstrap', ('--constraints', 'mem=2G', 405 'bar/baz', 'foo', 406 '--config', config_file.name, 407 '--default-model', 'foo', 408 '--agent-version', '2.0'), include_e=False) 409 config_file.seek(0) 410 config = yaml.safe_load(config_file) 411 self.assertEqual({'test-mode': True}, config) 412 413 def test_bootstrap_upload_tools(self): 414 env = JujuData('foo', {'type': 'foo', 'region': 'baz'}) 415 client = ModelClient(env, '2.0-zeta1', None) 416 with observable_temp_file() as config_file: 417 with patch_juju_call(client) as mock: 418 client.bootstrap(upload_tools=True) 419 mock.assert_called_with( 420 'bootstrap', ( 421 '--upload-tools', '--constraints', 'mem=2G', 422 'foo/baz', 'foo', 423 '--config', config_file.name, 424 '--default-model', 'foo'), include_e=False) 425 426 def test_bootstrap_credential(self): 427 env = JujuData('foo', {'type': 'foo', 'region': 'baz'}) 428 client = ModelClient(env, '2.0-zeta1', None) 429 with observable_temp_file() as config_file: 430 with patch_juju_call(client) as mock: 431 client.bootstrap(credential='credential_name') 432 mock.assert_called_with( 433 'bootstrap', ( 434 '--constraints', 'mem=2G', 435 'foo/baz', 'foo', 436 '--config', config_file.name, 437 '--default-model', 'foo', '--agent-version', '2.0', 438 '--credential', 'credential_name'), include_e=False) 439 440 def test_bootstrap_bootstrap_series(self): 441 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 442 client = ModelClient(env, '2.0-zeta1', None) 443 with patch_juju_call(client) as mock: 444 with observable_temp_file() as config_file: 445 client.bootstrap(bootstrap_series='angsty') 446 mock.assert_called_with( 447 'bootstrap', ( 448 '--constraints', 'mem=2G', 449 'bar/baz', 'foo', 450 '--config', config_file.name, '--default-model', 'foo', 451 '--agent-version', '2.0', 452 '--bootstrap-series', 'angsty'), include_e=False) 453 454 def test_bootstrap_auto_upgrade(self): 455 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 456 client = ModelClient(env, '2.0-zeta1', None) 457 with patch_juju_call(client) as mock: 458 with observable_temp_file() as config_file: 459 client.bootstrap(auto_upgrade=True) 460 mock.assert_called_with( 461 'bootstrap', ( 462 '--constraints', 'mem=2G', 463 'bar/baz', 'foo', 464 '--config', config_file.name, '--default-model', 'foo', 465 '--agent-version', '2.0', '--auto-upgrade'), include_e=False) 466 467 def test_bootstrap_no_gui(self): 468 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 469 client = ModelClient(env, '2.0-zeta1', None) 470 with patch_juju_call(client) as mock: 471 with observable_temp_file() as config_file: 472 client.bootstrap(no_gui=True) 473 mock.assert_called_with( 474 'bootstrap', ( 475 '--constraints', 'mem=2G', 476 'bar/baz', 'foo', 477 '--config', config_file.name, '--default-model', 'foo', 478 '--agent-version', '2.0', '--no-gui'), include_e=False) 479 480 def test_bootstrap_metadata(self): 481 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 482 client = ModelClient(env, '2.0-zeta1', None) 483 with patch_juju_call(client) as mock: 484 with observable_temp_file() as config_file: 485 client.bootstrap(metadata_source='/var/test-source') 486 mock.assert_called_with( 487 'bootstrap', ( 488 '--constraints', 'mem=2G', 489 'bar/baz', 'foo', 490 '--config', config_file.name, '--default-model', 'foo', 491 '--agent-version', '2.0', 492 '--metadata-source', '/var/test-source'), include_e=False) 493 494 def test_get_bootstrap_args_bootstrap_to(self): 495 env = JujuData( 496 'foo', {'type': 'bar', 'region': 'baz'}, bootstrap_to='zone=fnord') 497 client = ModelClient(env, '2.0-zeta1', None) 498 args = client.get_bootstrap_args( 499 upload_tools=False, config_filename='config') 500 self.assertEqual( 501 ('--constraints', 'mem=2G', 'bar/baz', 'foo', 502 '--config', 'config', '--default-model', 'foo', 503 '--agent-version', '2.0', '--to', 'zone=fnord'), 504 args) 505 506 def test_bootstrap_async(self): 507 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 508 with patch.object(ModelClient, 'juju_async', autospec=True) as mock: 509 client = ModelClient(env, '2.0-zeta1', None) 510 client.env.juju_home = 'foo' 511 with observable_temp_file() as config_file: 512 with client.bootstrap_async(): 513 mock.assert_called_once_with( 514 client, 'bootstrap', ( 515 '--constraints', 'mem=2G', 516 'bar/baz', 'foo', 517 '--config', config_file.name, 518 '--default-model', 'foo', 519 '--agent-version', '2.0'), include_e=False) 520 521 def test_bootstrap_async_upload_tools(self): 522 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 523 with patch.object(ModelClient, 'juju_async', autospec=True) as mock: 524 client = ModelClient(env, '2.0-zeta1', None) 525 with observable_temp_file() as config_file: 526 with client.bootstrap_async(upload_tools=True): 527 mock.assert_called_with( 528 client, 'bootstrap', ( 529 '--upload-tools', '--constraints', 'mem=2G', 530 'bar/baz', 'foo', 531 '--config', config_file.name, 532 '--default-model', 'foo', 533 ), 534 include_e=False) 535 536 def test_get_bootstrap_args_bootstrap_series(self): 537 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 538 client = ModelClient(env, '2.0-zeta1', None) 539 args = client.get_bootstrap_args(upload_tools=True, 540 config_filename='config', 541 bootstrap_series='angsty') 542 self.assertEqual(args, ( 543 '--upload-tools', '--constraints', 'mem=2G', 544 'bar/baz', 'foo', 545 '--config', 'config', '--default-model', 'foo', 546 '--bootstrap-series', 'angsty')) 547 548 def test_get_bootstrap_args_agent_version(self): 549 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 550 client = ModelClient(env, '2.0-zeta1', None) 551 args = client.get_bootstrap_args(upload_tools=False, 552 config_filename='config', 553 agent_version='2.0-lambda1') 554 self.assertEqual(('--constraints', 'mem=2G', 555 'bar/baz', 'foo', 556 '--config', 'config', '--default-model', 'foo', 557 '--agent-version', '2.0-lambda1'), args) 558 559 def test_get_bootstrap_args_upload_tools_and_agent_version(self): 560 env = JujuData('foo', {'type': 'bar', 'region': 'baz'}) 561 client = ModelClient(env, '2.0-zeta1', None) 562 with self.assertRaises(ValueError): 563 client.get_bootstrap_args(upload_tools=True, 564 config_filename='config', 565 agent_version='2.0-lambda1') 566 567 def test_add_model_hypenated_controller(self): 568 self.do_add_model('add-model', ('-c', 'foo')) 569 570 def do_add_model(self, create_cmd, controller_option): 571 controller_client = ModelClient(JujuData('foo'), None, None) 572 model_data = JujuData('bar', {'type': 'foo'}) 573 with patch_juju_call(controller_client) as ccj_mock: 574 with observable_temp_file() as config_file: 575 controller_client.add_model(model_data) 576 ccj_mock.assert_called_once_with( 577 create_cmd, controller_option + ( 578 'bar', '--config', config_file.name), include_e=False) 579 580 def test_add_model_explicit_region(self): 581 client = fake_juju_client() 582 client.bootstrap() 583 client.env.controller.explicit_region = True 584 model = client.env.clone('new-model') 585 with patch_juju_call(client._backend) as juju_mock: 586 with observable_temp_file() as config_file: 587 client.add_model(model) 588 juju_mock.assert_called_once_with('add-model', ( 589 '-c', 'name', 'new-model', 'foo/bar', '--credential', 'creds', 590 '--config', config_file.name), 591 frozenset({'migration'}), 'foo', None, True, None, None, 592 suppress_err=False) 593 594 def test_add_model_by_name(self): 595 client = fake_juju_client() 596 client.bootstrap() 597 with patch_juju_call(client._backend) as juju_mock: 598 with observable_temp_file() as config_file: 599 client.add_model('new-model') 600 juju_mock.assert_called_once_with('add-model', ( 601 '-c', 'name', 'new-model', '--config', config_file.name), 602 frozenset({'migration'}), 'foo', None, True, None, None, 603 suppress_err=False) 604 605 def test_destroy_model(self): 606 env = JujuData('foo', {'type': 'ec2'}) 607 client = ModelClient(env, None, None) 608 with patch_juju_call(client) as mock: 609 client.destroy_model() 610 mock.assert_called_with( 611 'destroy-model', ('foo:foo', '-y', '--destroy-storage'), 612 include_e=False, timeout=600) 613 614 def test_destroy_model_azure(self): 615 env = JujuData('foo', {'type': 'azure'}) 616 client = ModelClient(env, None, None) 617 with patch_juju_call(client) as mock: 618 client.destroy_model() 619 mock.assert_called_with( 620 'destroy-model', ('foo:foo', '-y', '--destroy-storage'), 621 include_e=False, timeout=2700) 622 623 def test_destroy_model_gce(self): 624 env = JujuData('foo', {'type': 'gce'}) 625 client = ModelClient(env, None, None) 626 with patch_juju_call(client) as mock: 627 client.destroy_model() 628 mock.assert_called_with( 629 'destroy-model', ('foo:foo', '-y', '--destroy-storage'), 630 include_e=False, timeout=1200) 631 632 def test_kill_controller(self): 633 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) 634 with patch_juju_call(client) as juju_mock: 635 client.kill_controller() 636 juju_mock.assert_called_once_with( 637 'kill-controller', ('foo', '-y'), check=False, include_e=False, 638 timeout=600) 639 640 def test_kill_controller_check(self): 641 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) 642 with patch_juju_call(client) as juju_mock: 643 client.kill_controller(check=True) 644 juju_mock.assert_called_once_with( 645 'kill-controller', ('foo', '-y'), check=True, include_e=False, 646 timeout=600) 647 648 def test_kill_controller_gce(self): 649 client = ModelClient(JujuData('foo', {'type': 'gce'}), None, None) 650 with patch_juju_call(client) as juju_mock: 651 client.kill_controller() 652 juju_mock.assert_called_once_with( 653 'kill-controller', ('foo', '-y'), check=False, include_e=False, 654 timeout=1200) 655 656 def test_destroy_controller(self): 657 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) 658 with patch_juju_call(client) as juju_mock: 659 client.destroy_controller() 660 juju_mock.assert_called_once_with( 661 'destroy-controller', ('foo', '-y'), include_e=False, 662 timeout=600) 663 664 def test_destroy_controller_all_models(self): 665 client = ModelClient(JujuData('foo', {'type': 'ec2'}), None, None) 666 with patch_juju_call(client) as juju_mock: 667 client.destroy_controller(all_models=True) 668 juju_mock.assert_called_once_with( 669 'destroy-controller', ('foo', '-y', '--destroy-all-models'), 670 include_e=False, timeout=600) 671 672 @contextmanager 673 def mock_tear_down(self, client, destroy_raises=False, kill_raises=False): 674 @contextmanager 675 def patch_raise(target, attribute, raises): 676 def raise_error(*args, **kwargs): 677 raise subprocess.CalledProcessError( 678 1, ('juju', attribute.replace('_', '-'), '-y')) 679 if raises: 680 with patch.object(target, attribute, autospec=True, 681 side_effect=raise_error) as mock: 682 yield mock 683 else: 684 with patch.object( 685 target, attribute, autospec=True, 686 return_value=make_fake_juju_return()) as mock: 687 yield mock 688 689 with patch_raise(client, 'destroy_controller', destroy_raises 690 ) as mock_destroy: 691 with patch_raise(client, 'kill_controller', kill_raises 692 ) as mock_kill: 693 yield (mock_destroy, mock_kill) 694 695 def test_tear_down(self): 696 """Check that a successful tear_down calls destroy.""" 697 client = ModelClient(JujuData('foo', {'type': 'gce'}), None, None) 698 with self.mock_tear_down(client) as (mock_destroy, mock_kill): 699 client.tear_down() 700 mock_destroy.assert_called_once_with(all_models=True) 701 self.assertIsFalse(mock_kill.called) 702 703 def test_tear_down_fall_back(self): 704 """Check that tear_down uses kill_controller if destroy fails.""" 705 client = ModelClient(JujuData('foo', {'type': 'gce'}), None, None) 706 with self.mock_tear_down(client, True) as (mock_destroy, mock_kill): 707 with self.assertRaises(subprocess.CalledProcessError) as err: 708 client.tear_down() 709 self.assertEqual('destroy-controller', err.exception.cmd[1]) 710 mock_destroy.assert_called_once_with(all_models=True) 711 mock_kill.assert_called_once_with() 712 713 def test_tear_down_double_fail(self): 714 """Check tear_down when both destroy and kill fail.""" 715 client = ModelClient(JujuData('foo', {'type': 'gce'}), None, None) 716 with self.mock_tear_down(client, True, True) as ( 717 mock_destroy, mock_kill): 718 with self.assertRaises(subprocess.CalledProcessError) as err: 719 client.tear_down() 720 self.assertEqual('kill-controller', err.exception.cmd[1]) 721 mock_destroy.assert_called_once_with(all_models=True) 722 mock_kill.assert_called_once_with() 723 724 def test_get_juju_output(self): 725 env = JujuData('foo') 726 client = ModelClient(env, None, 'juju') 727 fake_popen = FakePopen('asdf', None, 0) 728 with patch('subprocess.Popen', return_value=fake_popen) as mock: 729 result = client.get_juju_output('bar') 730 self.assertEqual('asdf'.encode('ascii'), result) 731 self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo:foo'),), 732 mock.call_args[0]) 733 734 def test_get_juju_output_accepts_varargs(self): 735 env = JujuData('foo') 736 fake_popen = FakePopen('asdf', None, 0) 737 client = ModelClient(env, None, 'juju') 738 with patch('subprocess.Popen', return_value=fake_popen) as mock: 739 result = client.get_juju_output('bar', 'baz', '--qux') 740 self.assertEqual('asdf'.encode('ascii'), result) 741 self.assertEqual((('juju', '--show-log', 'bar', '-m', 'foo:foo', 'baz', 742 '--qux'),), mock.call_args[0]) 743 744 def test_get_juju_output_stderr(self): 745 env = JujuData('foo') 746 fake_popen = FakePopen(None, 'Hello!', 1) 747 client = ModelClient(env, None, 'juju') 748 with self.assertRaises(subprocess.CalledProcessError) as exc: 749 with patch('subprocess.Popen', return_value=fake_popen): 750 client.get_juju_output('bar') 751 self.assertEqual(exc.exception.stderr, 'Hello!'.encode('ascii')) 752 753 def test_get_juju_output_merge_stderr(self): 754 env = JujuData('foo') 755 fake_popen = FakePopen('Err on out', None, 0) 756 client = ModelClient(env, None, 'juju') 757 with patch('subprocess.Popen', return_value=fake_popen) as mock_popen: 758 result = client.get_juju_output('bar', merge_stderr=True) 759 self.assertEqual(result, 'Err on out'.encode('ascii')) 760 mock_popen.assert_called_once_with( 761 ('juju', '--show-log', 'bar', '-m', 'foo:foo'), 762 stdin=subprocess.PIPE, stderr=subprocess.STDOUT, 763 stdout=subprocess.PIPE) 764 765 def test_get_juju_output_full_cmd(self): 766 env = JujuData('foo') 767 fake_popen = FakePopen(None, 'Hello!', 1) 768 client = ModelClient(env, None, 'juju') 769 with self.assertRaises(subprocess.CalledProcessError) as exc: 770 with patch('subprocess.Popen', return_value=fake_popen): 771 client.get_juju_output('bar', '--baz', 'qux') 772 self.assertEqual( 773 ('juju', '--show-log', 'bar', '-m', 'foo:foo', '--baz', 'qux'), 774 exc.exception.cmd) 775 776 def test_get_juju_output_accepts_timeout(self): 777 env = JujuData('foo') 778 fake_popen = FakePopen('asdf', None, 0) 779 client = ModelClient(env, None, 'juju') 780 with patch('subprocess.Popen', return_value=fake_popen) as po_mock: 781 client.get_juju_output('bar', timeout=5) 782 self.assertEqual( 783 po_mock.call_args[0][0], 784 (sys.executable, get_timeout_path(), '5.00', '--', 'juju', 785 '--show-log', 'bar', '-m', 'foo:foo')) 786 787 def test__shell_environ_juju_data(self): 788 client = ModelClient( 789 JujuData('baz', {'type': 'ec2'}), '1.25-foobar', 'path', 'asdf') 790 env = client._shell_environ() 791 self.assertEqual(env['JUJU_DATA'], 'asdf') 792 self.assertNotIn('JUJU_HOME', env) 793 794 def test_juju_output_supplies_path(self): 795 env = JujuData('foo') 796 client = ModelClient(env, None, '/foobar/bar') 797 798 def check_path(*args, **kwargs): 799 self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:') 800 return FakePopen(None, None, 0) 801 with patch('subprocess.Popen', autospec=True, 802 side_effect=check_path): 803 client.get_juju_output('cmd', 'baz') 804 805 def test_get_status(self): 806 output_text = dedent("""\ 807 - a 808 - b 809 - c 810 """).encode('ascii') 811 env = JujuData('foo') 812 client = ModelClient(env, None, None) 813 with patch.object(client, 'get_juju_output', 814 return_value=output_text) as gjo_mock: 815 result = client.get_status() 816 gjo_mock.assert_called_once_with( 817 'show-status', '--format', 'yaml', controller=False) 818 self.assertEqual(Status, type(result)) 819 self.assertEqual(['a', 'b', 'c'], result.status) 820 821 def test_get_status_retries_on_error(self): 822 env = JujuData('foo') 823 client = ModelClient(env, None, None) 824 client.attempt = 0 825 826 def get_juju_output(command, *args, **kwargs): 827 if client.attempt == 1: 828 return '"hello"'.encode('ascii') 829 client.attempt += 1 830 raise subprocess.CalledProcessError(1, command) 831 832 with patch.object(client, 'get_juju_output', get_juju_output): 833 client.get_status() 834 835 def test_get_status_raises_on_timeout_1(self): 836 env = JujuData('foo') 837 client = ModelClient(env, None, None) 838 839 def get_juju_output(command, *args, **kwargs): 840 raise subprocess.CalledProcessError(1, command) 841 842 with patch.object(client, 'get_juju_output', 843 side_effect=get_juju_output): 844 with patch('jujupy.client.until_timeout', 845 lambda x: iter([None, None])): 846 with self.assertRaisesRegexp( 847 Exception, 'Timed out waiting for juju status'): 848 with patch('jujupy.client.time.sleep'): 849 client.get_status() 850 851 def test_get_status_raises_on_timeout_2(self): 852 env = JujuData('foo') 853 client = ModelClient(env, None, None) 854 with patch('jujupy.client.until_timeout', 855 return_value=iter([1])) as mock_ut: 856 with patch.object(client, 'get_juju_output', 857 side_effect=StopIteration): 858 with self.assertRaises(StopIteration): 859 with patch('jujupy.client.time.sleep'): 860 client.get_status(500) 861 mock_ut.assert_called_with(500) 862 863 def test_show_model_uses_provided_model_name(self): 864 env = JujuData('foo') 865 client = ModelClient(env, None, None) 866 show_model_output = dedent("""\ 867 bar: 868 status: 869 current: available 870 since: 4 minutes ago 871 migration: 'Some message.' 872 migration-start: 48 seconds ago 873 """) 874 with patch.object( 875 client, 'get_juju_output', 876 autospect=True, return_value=show_model_output) as m_gjo: 877 output = client.show_model('bar') 878 self.assertItemsEqual(['bar'], output.keys()) 879 m_gjo.assert_called_once_with( 880 'show-model', 'foo:bar', '--format', 'yaml', include_e=False) 881 882 def test_show_model_defaults_to_own_model_name(self): 883 env = JujuData('foo') 884 client = ModelClient(env, None, None) 885 show_model_output = dedent("""\ 886 foo: 887 status: 888 current: available 889 since: 4 minutes ago 890 migration: 'Some message.' 891 migration-start: 48 seconds ago 892 """) 893 with patch.object( 894 client, 'get_juju_output', 895 autospect=True, return_value=show_model_output) as m_gjo: 896 output = client.show_model() 897 self.assertItemsEqual(['foo'], output.keys()) 898 m_gjo.assert_called_once_with( 899 'show-model', 'foo:foo', '--format', 'yaml', include_e=False) 900 901 @staticmethod 902 def make_status_yaml(key, machine_value, unit_value): 903 return dedent("""\ 904 model: 905 name: foo 906 machines: 907 "0": 908 {0}: {1} 909 applications: 910 jenkins: 911 units: 912 jenkins/0: 913 {0}: {2} 914 """.format(key, machine_value, unit_value)).encode('ascii') 915 916 def test_deploy_non_joyent(self): 917 env = ModelClient( 918 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 919 with patch_juju_call(env) as mock_juju: 920 env.deploy('mondogb') 921 mock_juju.assert_called_with('deploy', ('mondogb',)) 922 923 def test_deploy_joyent(self): 924 env = ModelClient( 925 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 926 with patch_juju_call(env) as mock_juju: 927 env.deploy('mondogb') 928 mock_juju.assert_called_with('deploy', ('mondogb',)) 929 930 def test_deploy_repository(self): 931 env = ModelClient( 932 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 933 with patch_juju_call(env) as mock_juju: 934 env.deploy('/home/jrandom/repo/mongodb') 935 mock_juju.assert_called_with( 936 'deploy', ('/home/jrandom/repo/mongodb',)) 937 938 def test_deploy_to(self): 939 env = ModelClient( 940 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 941 with patch_juju_call(env) as mock_juju: 942 env.deploy('mondogb', to='0') 943 mock_juju.assert_called_with( 944 'deploy', ('mondogb', '--to', '0')) 945 946 def test_deploy_service(self): 947 env = ModelClient( 948 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 949 with patch_juju_call(env) as mock_juju: 950 env.deploy('local:mondogb', service='my-mondogb') 951 mock_juju.assert_called_with( 952 'deploy', ('local:mondogb', 'my-mondogb',)) 953 954 def test_deploy_force(self): 955 env = ModelClient( 956 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 957 with patch_juju_call(env) as mock_juju: 958 env.deploy('local:mondogb', force=True) 959 mock_juju.assert_called_with('deploy', ('local:mondogb', '--force',)) 960 961 def test_deploy_xenial_series(self): 962 env = ModelClient( 963 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 964 with patch_juju_call(env) as mock_juju: 965 env.deploy('local:blah', series='xenial') 966 mock_juju.assert_called_with( 967 'deploy', ('local:blah', '--series', 'xenial')) 968 969 def test_deploy_bionic_series(self): 970 env = ModelClient( 971 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 972 with patch_juju_call(env) as mock_juju: 973 env.deploy('local:blah', series='bionic') 974 mock_juju.assert_called_with( 975 'deploy', ('local:blah', '--series', 'bionic')) 976 977 def test_deploy_multiple(self): 978 env = ModelClient( 979 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 980 with patch_juju_call(env) as mock_juju: 981 env.deploy('local:blah', num=2) 982 mock_juju.assert_called_with( 983 'deploy', ('local:blah', '-n', '2')) 984 985 def test_deploy_resource(self): 986 env = ModelClient(JujuData('foo', {'type': 'lxd'}), None, None) 987 with patch_juju_call(env) as mock_juju: 988 env.deploy('local:blah', resource='foo=/path/dir') 989 mock_juju.assert_called_with( 990 'deploy', ('local:blah', '--resource', 'foo=/path/dir')) 991 992 def test_deploy_storage(self): 993 env = ModelClient( 994 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 995 with patch_juju_call(env) as mock_juju: 996 env.deploy('mondogb', storage='rootfs,1G') 997 mock_juju.assert_called_with( 998 'deploy', ('mondogb', '--storage', 'rootfs,1G')) 999 1000 def test_deploy_constraints(self): 1001 env = ModelClient( 1002 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 1003 with patch_juju_call(env) as mock_juju: 1004 env.deploy('mondogb', constraints='virt-type=kvm') 1005 mock_juju.assert_called_with( 1006 'deploy', ('mondogb', '--constraints', 'virt-type=kvm')) 1007 1008 def test_deploy_bind(self): 1009 env = ModelClient(JujuData('foo', {'type': 'lxd'}), None, None) 1010 with patch_juju_call(env) as mock_juju: 1011 env.deploy('mydb', bind='backspace') 1012 mock_juju.assert_called_with('deploy', ('mydb', '--bind', 'backspace')) 1013 1014 def test_deploy_aliased(self): 1015 env = ModelClient(JujuData('foo', {'type': 'lxd'}), None, None) 1016 with patch_juju_call(env) as mock_juju: 1017 env.deploy('local:blah', alias='blah-blah') 1018 mock_juju.assert_called_with( 1019 'deploy', ('local:blah', 'blah-blah')) 1020 1021 def test_attach(self): 1022 env = ModelClient(JujuData('foo', {'type': 'lxd'}), None, None) 1023 with patch_juju_call(env) as mock_juju: 1024 env.attach('foo', resource='foo=/path/dir') 1025 mock_juju.assert_called_with('attach', ('foo', 'foo=/path/dir')) 1026 1027 def test_list_resources(self): 1028 data = 'resourceid: resource/foo' 1029 client = ModelClient(JujuData('lxd'), None, None) 1030 with patch.object( 1031 client, 'get_juju_output', return_value=data) as mock_gjo: 1032 status = client.list_resources('foo') 1033 self.assertEqual(status, yaml.safe_load(data)) 1034 mock_gjo.assert_called_with( 1035 'list-resources', '--format', 'yaml', 'foo', '--details') 1036 1037 def test_wait_for_resource(self): 1038 client = ModelClient(JujuData('lxd'), None, None) 1039 with patch.object( 1040 client, 'list_resources', 1041 return_value=make_resource_list()) as mock_lr: 1042 client.wait_for_resource('dummy-resource/foo', 'foo') 1043 mock_lr.assert_called_once_with('foo') 1044 1045 def test_wait_for_resource_timeout(self): 1046 client = ModelClient(JujuData('lxd'), None, None) 1047 resource_list = make_resource_list() 1048 resource_list['resources'][0]['expected']['resourceid'] = 'bad_id' 1049 with patch.object( 1050 client, 'list_resources', 1051 return_value=resource_list) as mock_lr: 1052 with patch('jujupy.client.until_timeout', autospec=True, 1053 return_value=[0, 1]) as mock_ju: 1054 with patch('time.sleep', autospec=True) as mock_ts: 1055 with self.assertRaisesRegexp( 1056 JujuResourceTimeout, 1057 'Timeout waiting for a resource to be downloaded'): 1058 client.wait_for_resource('dummy-resource/foo', 'foo') 1059 calls = [call('foo'), call('foo')] 1060 self.assertEqual(mock_lr.mock_calls, calls) 1061 self.assertEqual(mock_ts.mock_calls, [call(.1), call(.1)]) 1062 self.assertEqual(mock_ju.mock_calls, [call(60)]) 1063 1064 def test_wait_for_resource_suppresses_deadline(self): 1065 client = ModelClient(JujuData('lxd', juju_home=''), None, None) 1066 with client_past_deadline(client): 1067 real_check_timeouts = client.check_timeouts 1068 1069 def list_resources(service_or_unit): 1070 with real_check_timeouts(): 1071 return make_resource_list() 1072 1073 with patch.object(client, 'check_timeouts', autospec=True): 1074 with patch.object(client, 'list_resources', autospec=True, 1075 side_effect=list_resources): 1076 client.wait_for_resource('dummy-resource/foo', 1077 'app_unit') 1078 1079 def test_wait_for_resource_checks_deadline(self): 1080 resource_list = make_resource_list() 1081 client = ModelClient(JujuData('lxd', juju_home=''), None, None) 1082 with client_past_deadline(client): 1083 with patch.object(client, 'list_resources', autospec=True, 1084 return_value=resource_list): 1085 with self.assertRaises(SoftDeadlineExceeded): 1086 client.wait_for_resource('dummy-resource/foo', 'app_unit') 1087 1088 def test_deploy_bundle_2x(self): 1089 client = ModelClient(JujuData('an_env', None), 1090 '1.23-series-arch', None) 1091 with patch_juju_call(client) as mock_juju: 1092 client.deploy_bundle('bundle:~juju-qa/some-bundle') 1093 mock_juju.assert_called_with( 1094 'deploy', ('bundle:~juju-qa/some-bundle'), timeout=3600) 1095 1096 def test_deploy_bundle_template(self): 1097 client = ModelClient(JujuData('an_env', None), 1098 '1.23-series-arch', None) 1099 with patch_juju_call(client) as mock_juju: 1100 client.deploy_bundle('bundle:~juju-qa/some-{container}-bundle') 1101 mock_juju.assert_called_with( 1102 'deploy', ('bundle:~juju-qa/some-lxd-bundle'), timeout=3600) 1103 1104 def test_upgrade_charm(self): 1105 env = ModelClient( 1106 JujuData('foo', {'type': 'lxd'}), '2.34-74', None) 1107 with patch_juju_call(env) as mock_juju: 1108 env.upgrade_charm('foo-service', 1109 '/bar/repository/angsty/mongodb') 1110 mock_juju.assert_called_once_with( 1111 'upgrade-charm', ('foo-service', '--path', 1112 '/bar/repository/angsty/mongodb',)) 1113 1114 def test_remove_service(self): 1115 env = ModelClient( 1116 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 1117 with patch_juju_call(env) as mock_juju: 1118 env.remove_service('mondogb') 1119 mock_juju.assert_called_with('remove-application', ('mondogb',)) 1120 1121 def test_status_until_always_runs_once(self): 1122 client = ModelClient( 1123 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 1124 status_txt = self.make_status_yaml('agent-state', 'started', 'started') 1125 with patch.object(client, 'get_juju_output', return_value=status_txt): 1126 result = list(client.status_until(-1)) 1127 self.assertEqual( 1128 [r.status for r in result], 1129 [Status.from_text(status_txt.decode('ascii')).status]) 1130 1131 def test_status_until_timeout(self): 1132 client = ModelClient( 1133 JujuData('foo', {'type': 'lxd'}), '1.234-76', None) 1134 status_txt = self.make_status_yaml('agent-state', 'started', 'started') 1135 status_yaml = yaml.safe_load(status_txt) 1136 1137 def until_timeout_stub(timeout, start=None): 1138 return iter([None, None]) 1139 1140 with patch.object(client, 'get_juju_output', return_value=status_txt): 1141 with patch('jujupy.client.until_timeout', 1142 side_effect=until_timeout_stub) as ut_mock: 1143 with patch('jujupy.client.time.sleep'): 1144 result = list(client.status_until(30, 70)) 1145 self.assertEqual( 1146 [r.status for r in result], [status_yaml] * 3) 1147 # until_timeout is called by status as well as status_until. 1148 self.assertEqual(ut_mock.mock_calls, 1149 [call(60), call(30, start=70), call(60), call(60)]) 1150 1151 def test_status_until_suppresses_deadline(self): 1152 with self.only_status_checks() as client: 1153 list(client.status_until(0)) 1154 1155 def test_status_until_checks_deadline(self): 1156 with self.status_does_not_check() as client: 1157 with self.assertRaises(SoftDeadlineExceeded): 1158 list(client.status_until(0)) 1159 1160 def test_add_ssh_machines(self): 1161 client = ModelClient(JujuData('foo'), None, 'juju') 1162 with patch('subprocess.check_call', autospec=True) as cc_mock: 1163 client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz']) 1164 assert_juju_call( 1165 self, 1166 cc_mock, 1167 client, 1168 ('juju', '--show-log', 'add-machine', 1169 '-m', 'foo:foo', 'ssh:m-foo'), 1170 0) 1171 assert_juju_call( 1172 self, 1173 cc_mock, 1174 client, 1175 ('juju', '--show-log', 'add-machine', 1176 '-m', 'foo:foo', 'ssh:m-bar'), 1177 1) 1178 assert_juju_call( 1179 self, 1180 cc_mock, 1181 client, 1182 ('juju', '--show-log', 'add-machine', 1183 '-m', 'foo:foo', 'ssh:m-baz'), 1184 2) 1185 self.assertEqual(cc_mock.call_count, 3) 1186 1187 def test_make_remove_machine_condition(self): 1188 client = fake_juju_client() 1189 condition = client.make_remove_machine_condition('0') 1190 self.assertIs(WaitMachineNotPresent, type(condition)) 1191 self.assertEqual('0', condition.machine) 1192 self.assertEqual(600, condition.timeout) 1193 1194 def test_make_remove_machine_condition_azure(self): 1195 client = fake_juju_client() 1196 client.env._config['type'] = 'azure' 1197 condition = client.make_remove_machine_condition('0') 1198 self.assertIs(WaitMachineNotPresent, type(condition)) 1199 self.assertEqual('0', condition.machine) 1200 self.assertEqual(1200, condition.timeout) 1201 1202 def test_add_ssh_machines_retry(self): 1203 client = ModelClient(JujuData('foo'), None, 'juju') 1204 with patch('subprocess.check_call', autospec=True, 1205 side_effect=[subprocess.CalledProcessError(None, None), 1206 None, None, None]) as cc_mock: 1207 client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz']) 1208 assert_juju_call( 1209 self, 1210 cc_mock, 1211 client, 1212 ('juju', '--show-log', 'add-machine', 1213 '-m', 'foo:foo', 'ssh:m-foo'), 1214 0) 1215 self.pause_mock.assert_called_once_with(30) 1216 assert_juju_call( 1217 self, 1218 cc_mock, 1219 client, 1220 ('juju', '--show-log', 'add-machine', 1221 '-m', 'foo:foo', 'ssh:m-foo'), 1222 1) 1223 assert_juju_call( 1224 self, 1225 cc_mock, 1226 client, 1227 ('juju', '--show-log', 'add-machine', 1228 '-m', 'foo:foo', 'ssh:m-bar'), 1229 2) 1230 assert_juju_call( 1231 self, 1232 cc_mock, 1233 client, 1234 ('juju', '--show-log', 'add-machine', 1235 '-m', 'foo:foo', 'ssh:m-baz'), 1236 3) 1237 self.assertEqual(cc_mock.call_count, 4) 1238 1239 def test_add_ssh_machines_fail_on_second_machine(self): 1240 client = ModelClient(JujuData('foo'), None, 'juju') 1241 with patch('subprocess.check_call', autospec=True, side_effect=[ 1242 None, subprocess.CalledProcessError(None, None), None, None 1243 ]) as cc_mock: 1244 with self.assertRaises(subprocess.CalledProcessError): 1245 client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz']) 1246 assert_juju_call( 1247 self, 1248 cc_mock, 1249 client, 1250 ('juju', '--show-log', 'add-machine', 1251 '-m', 'foo:foo', 'ssh:m-foo'), 1252 0) 1253 assert_juju_call( 1254 self, 1255 cc_mock, 1256 client, 1257 ('juju', '--show-log', 'add-machine', 1258 '-m', 'foo:foo', 'ssh:m-bar'), 1259 1) 1260 self.assertEqual(cc_mock.call_count, 2) 1261 1262 def test_add_ssh_machines_fail_on_second_attempt(self): 1263 client = ModelClient(JujuData('foo'), None, 'juju') 1264 with patch('subprocess.check_call', autospec=True, side_effect=[ 1265 subprocess.CalledProcessError(None, None), 1266 subprocess.CalledProcessError(None, None)]) as cc_mock: 1267 with self.assertRaises(subprocess.CalledProcessError): 1268 client.add_ssh_machines(['m-foo', 'm-bar', 'm-baz']) 1269 assert_juju_call( 1270 self, 1271 cc_mock, 1272 client, 1273 ('juju', '--show-log', 'add-machine', 1274 '-m', 'foo:foo', 'ssh:m-foo'), 1275 0) 1276 assert_juju_call( 1277 self, 1278 cc_mock, 1279 client, 1280 ('juju', '--show-log', 'add-machine', 1281 '-m', 'foo:foo', 'ssh:m-foo'), 1282 1) 1283 self.assertEqual(cc_mock.call_count, 2) 1284 1285 def test_remove_machine(self): 1286 client = fake_juju_client() 1287 with patch_juju_call(client._backend) as juju_mock: 1288 condition = client.remove_machine('0') 1289 call = backend_call( 1290 client, 'remove-machine', ('0',), 'name:name') 1291 juju_mock.assert_called_once_with(*call[1], **call[2]) 1292 self.assertEqual(condition, WaitMachineNotPresent('0', 600)) 1293 1294 def test_remove_machine_force(self): 1295 client = fake_juju_client() 1296 with patch_juju_call(client._backend) as juju_mock: 1297 client.remove_machine('0', force=True) 1298 call = backend_call( 1299 client, 'remove-machine', ('--force', '0'), 'name:name') 1300 juju_mock.assert_called_once_with(*call[1], **call[2]) 1301 1302 def test_remove_machine_azure(self): 1303 client = fake_juju_client(JujuData('name', { 1304 'type': 'azure', 1305 'location': 'usnorth', 1306 })) 1307 client.bootstrap() 1308 client.juju('add-machine', ()) 1309 condition = client.remove_machine('0') 1310 self.assertEqual(condition, WaitMachineNotPresent('0', 1200)) 1311 1312 def test_wait_for_started(self): 1313 value = self.make_status_yaml('agent-state', 'started', 'started') 1314 client = ModelClient(JujuData('lxd'), None, None) 1315 with patch.object(client, 'get_juju_output', return_value=value): 1316 client.wait_for_started() 1317 1318 def test_wait_for_started_timeout(self): 1319 value = self.make_status_yaml('agent-state', 'pending', 'started') 1320 client = ModelClient(JujuData('lxd'), None, None) 1321 with patch('jujupy.client.until_timeout', 1322 lambda x, start=None: range(1)): 1323 with patch.object(client, 'get_juju_output', return_value=value): 1324 writes = [] 1325 with patch.object(GroupReporter, '_write', autospec=True, 1326 side_effect=lambda _, s: writes.append(s)): 1327 with self.assertRaisesRegexp( 1328 StatusNotMet, 1329 'Timed out waiting for agents to start in lxd'): 1330 with patch('jujupy.client.time.sleep'): 1331 client.wait_for_started() 1332 self.assertEqual(writes, ['pending: 0', ' .', '\n']) 1333 1334 def test_wait_for_started_start(self): 1335 value = self.make_status_yaml('agent-state', 'started', 'pending') 1336 client = ModelClient(JujuData('lxd'), None, None) 1337 now = datetime.now() + timedelta(days=1) 1338 with patch('utility.until_timeout.now', return_value=now): 1339 with patch.object(client, 'get_juju_output', return_value=value): 1340 writes = [] 1341 with patch.object(GroupReporter, '_write', autospec=True, 1342 side_effect=lambda _, s: writes.append(s)): 1343 with self.assertRaisesRegexp( 1344 StatusNotMet, 1345 'Timed out waiting for agents to start in lxd'): 1346 with patch('jujupy.client.time.sleep'): 1347 client.wait_for_started( 1348 start=now - timedelta(1200)) 1349 self.assertEqual(writes, ['pending: jenkins/0', '\n']) 1350 1351 def make_ha_status(self, voting='has-vote'): 1352 return {'machines': { 1353 '0': {'controller-member-status': voting}, 1354 '1': {'controller-member-status': voting}, 1355 '2': {'controller-member-status': voting}, 1356 }} 1357 1358 @contextmanager 1359 def only_status_checks(self, client=None, status=None): 1360 """This context manager ensure only get_status calls check_timeouts. 1361 1362 Everything else will get a mock object. 1363 1364 Also, the client is patched so that the soft_deadline has been hit. 1365 """ 1366 if client is None: 1367 client = ModelClient(JujuData('lxd', juju_home=''), None, None) 1368 with client_past_deadline(client): 1369 # This will work even after we patch check_timeouts below. 1370 real_check_timeouts = client.check_timeouts 1371 1372 def check(timeout=60, controller=False): 1373 with real_check_timeouts(): 1374 return client.status_class(status, '') 1375 1376 with patch.object(client, 'get_status', autospec=True, 1377 side_effect=check): 1378 with patch.object(client, 'check_timeouts', autospec=True): 1379 yield client 1380 1381 def test__wait_for_status_suppresses_deadline(self): 1382 1383 def translate(x): 1384 return None 1385 1386 with self.only_status_checks() as client: 1387 client._wait_for_status(Mock(), translate) 1388 1389 @contextmanager 1390 def status_does_not_check(self, client=None, status=None): 1391 """This context manager ensure get_status never calls check_timeouts. 1392 1393 Also, the client is patched so that the soft_deadline has been hit. 1394 """ 1395 if client is None: 1396 client = ModelClient(JujuData('lxd', juju_home=''), None, None) 1397 with client_past_deadline(client): 1398 status_obj = client.status_class(status, '') 1399 with patch.object(client, 'get_status', autospec=True, 1400 return_value=status_obj): 1401 yield client 1402 1403 def test__wait_for_status_checks_deadline(self): 1404 1405 def translate(x): 1406 return None 1407 1408 with self.status_does_not_check() as client: 1409 with self.assertRaises(SoftDeadlineExceeded): 1410 client._wait_for_status(Mock(), translate) 1411 1412 @contextmanager 1413 def client_status_errors(self, client, errors): 1414 """Patch get_status().iter_errors keeping ignore_recoverable.""" 1415 def fake_iter_errors(ignore_recoverable): 1416 for error in errors.pop(0): 1417 if not (ignore_recoverable and error.recoverable): 1418 yield error 1419 1420 with patch.object(client.get_status(), 'iter_errors', autospec=True, 1421 side_effect=fake_iter_errors) as errors_mock: 1422 yield errors_mock 1423 1424 def test__wait_for_status_no_error(self): 1425 def translate(x): 1426 return {'waiting': '0'} 1427 1428 errors = [[], []] 1429 with self.status_does_not_check() as client: 1430 with self.client_status_errors(client, errors) as errors_mock: 1431 with self.assertRaises(StatusNotMet): 1432 with patch('jujupy.client.time.sleep'): 1433 client._wait_for_status(Mock(), translate, timeout=0) 1434 errors_mock.assert_has_calls( 1435 [call(ignore_recoverable=True), call(ignore_recoverable=False)]) 1436 1437 def test__wait_for_status_raises_error(self): 1438 def translate(x): 1439 return {'waiting': '0'} 1440 1441 errors = [[MachineError('0', 'error not recoverable')]] 1442 with self.status_does_not_check() as client: 1443 with self.client_status_errors(client, errors) as errors_mock: 1444 with self.assertRaises(MachineError): 1445 with patch('jujupy.client.time.sleep'): 1446 client._wait_for_status(Mock(), translate, timeout=0) 1447 errors_mock.assert_called_once_with(ignore_recoverable=True) 1448 1449 def test__wait_for_status_delays_recoverable(self): 1450 def translate(x): 1451 return {'waiting': '0'} 1452 1453 errors = [[StatusError('fake', 'error is recoverable')], 1454 [UnitError('fake/0', 'error is recoverable')]] 1455 with self.status_does_not_check() as client: 1456 with self.client_status_errors(client, errors) as errors_mock: 1457 with self.assertRaises(UnitError): 1458 with patch('jujupy.client.time.sleep'): 1459 client._wait_for_status(Mock(), translate, timeout=0) 1460 self.assertEqual(2, errors_mock.call_count) 1461 errors_mock.assert_has_calls( 1462 [call(ignore_recoverable=True), call(ignore_recoverable=False)]) 1463 1464 def test_wait_for_started_logs_status(self): 1465 value = self.make_status_yaml('agent-state', 'pending', 'started') 1466 client = ModelClient(JujuData('lxd'), None, None) 1467 with patch.object(client, 'get_juju_output', return_value=value): 1468 writes = [] 1469 with patch.object(GroupReporter, '_write', autospec=True, 1470 side_effect=lambda _, s: writes.append(s)): 1471 with self.assertRaisesRegexp( 1472 StatusNotMet, 1473 'Timed out waiting for agents to start in lxd'): 1474 with patch('jujupy.client.time.sleep'): 1475 client.wait_for_started(0) 1476 self.assertEqual(writes, ['pending: 0', '\n']) 1477 self.assertEqual( 1478 self.log_stream.getvalue(), 'ERROR %s\n' % value.decode('ascii')) 1479 1480 def test_wait_for_subordinate_units(self): 1481 value = dedent("""\ 1482 machines: 1483 "0": 1484 agent-state: started 1485 services: 1486 jenkins: 1487 units: 1488 jenkins/0: 1489 subordinates: 1490 sub1/0: 1491 agent-state: started 1492 ubuntu: 1493 units: 1494 ubuntu/0: 1495 subordinates: 1496 sub2/0: 1497 agent-state: started 1498 sub3/0: 1499 agent-state: started 1500 """).encode('ascii') 1501 client = ModelClient(JujuData('lxd'), None, None) 1502 now = datetime.now() + timedelta(days=1) 1503 with patch('utility.until_timeout.now', return_value=now): 1504 with patch.object(client, 'get_juju_output', return_value=value): 1505 with patch( 1506 'jujupy.client.GroupReporter.update') as update_mock: 1507 with patch( 1508 'jujupy.client.GroupReporter.finish' 1509 ) as finish_mock: 1510 with patch('jujupy.client.time.sleep'): 1511 client.wait_for_subordinate_units( 1512 'jenkins', 'sub1', start=now - timedelta(1200)) 1513 self.assertEqual([], update_mock.call_args_list) 1514 finish_mock.assert_called_once_with() 1515 1516 def test_wait_for_subordinate_units_with_agent_status(self): 1517 value = dedent("""\ 1518 machines: 1519 "0": 1520 agent-state: started 1521 services: 1522 jenkins: 1523 units: 1524 jenkins/0: 1525 subordinates: 1526 sub1/0: 1527 agent-status: 1528 current: idle 1529 ubuntu: 1530 units: 1531 ubuntu/0: 1532 subordinates: 1533 sub2/0: 1534 agent-status: 1535 current: idle 1536 sub3/0: 1537 agent-status: 1538 current: idle 1539 """).encode('ascii') 1540 client = ModelClient(JujuData('lxd'), None, None) 1541 now = datetime.now() + timedelta(days=1) 1542 with patch('utility.until_timeout.now', return_value=now): 1543 with patch.object(client, 'get_juju_output', return_value=value): 1544 with patch( 1545 'jujupy.client.GroupReporter.update') as update_mock: 1546 with patch( 1547 'jujupy.client.GroupReporter.finish' 1548 ) as finish_mock: 1549 with patch('jujupy.client.time.sleep'): 1550 client.wait_for_subordinate_units( 1551 'jenkins', 'sub1', start=now - timedelta(1200)) 1552 self.assertEqual([], update_mock.call_args_list) 1553 finish_mock.assert_called_once_with() 1554 1555 def test_wait_for_multiple_subordinate_units(self): 1556 value = dedent("""\ 1557 machines: 1558 "0": 1559 agent-state: started 1560 services: 1561 ubuntu: 1562 units: 1563 ubuntu/0: 1564 subordinates: 1565 sub/0: 1566 agent-state: started 1567 ubuntu/1: 1568 subordinates: 1569 sub/1: 1570 agent-state: started 1571 """).encode('ascii') 1572 client = ModelClient(JujuData('lxd'), None, None) 1573 now = datetime.now() + timedelta(days=1) 1574 with patch('utility.until_timeout.now', return_value=now): 1575 with patch.object(client, 'get_juju_output', return_value=value): 1576 with patch( 1577 'jujupy.client.GroupReporter.update') as update_mock: 1578 with patch( 1579 'jujupy.client.GroupReporter.finish' 1580 ) as finish_mock: 1581 with patch('jujupy.client.time.sleep'): 1582 client.wait_for_subordinate_units( 1583 'ubuntu', 'sub', start=now - timedelta(1200)) 1584 self.assertEqual([], update_mock.call_args_list) 1585 finish_mock.assert_called_once_with() 1586 1587 def test_wait_for_subordinate_units_checks_slash_in_unit_name(self): 1588 value = dedent("""\ 1589 machines: 1590 "0": 1591 agent-state: started 1592 applications: 1593 jenkins: 1594 units: 1595 jenkins/0: 1596 subordinates: 1597 sub1: 1598 agent-state: started 1599 """).encode('ascii') 1600 client = ModelClient(JujuData('lxd'), None, None) 1601 now = datetime.now() + timedelta(days=1) 1602 with patch('utility.until_timeout.now', return_value=now): 1603 with patch.object(client, 'get_juju_output', return_value=value): 1604 with self.assertRaisesRegexp( 1605 StatusNotMet, 1606 'Timed out waiting for agents to start in lxd'): 1607 with patch('jujupy.client.time.sleep'): 1608 client.wait_for_subordinate_units( 1609 'jenkins', 'sub1', start=now - timedelta(1200)) 1610 1611 def test_wait_for_subordinate_units_no_subordinate(self): 1612 value = dedent("""\ 1613 machines: 1614 "0": 1615 agent-state: started 1616 applications: 1617 jenkins: 1618 units: 1619 jenkins/0: 1620 agent-state: started 1621 """).encode('ascii') 1622 client = ModelClient(JujuData('lxd'), None, None) 1623 now = datetime.now() + timedelta(days=1) 1624 with patch('utility.until_timeout.now', return_value=now): 1625 with patch.object(client, 'get_juju_output', return_value=value): 1626 with self.assertRaisesRegexp( 1627 StatusNotMet, 1628 'Timed out waiting for agents to start in lxd'): 1629 with patch('jujupy.client.time.sleep'): 1630 client.wait_for_subordinate_units( 1631 'jenkins', 'sub1', start=now - timedelta(1200)) 1632 1633 def test_wait_for_workload(self): 1634 initial_status = Status.from_text("""\ 1635 machines: {} 1636 applications: 1637 jenkins: 1638 units: 1639 jenkins/0: 1640 workload-status: 1641 current: waiting 1642 subordinates: 1643 ntp/0: 1644 workload-status: 1645 current: unknown 1646 """) 1647 final_status = Status(copy.deepcopy(initial_status.status), None) 1648 final_status.status['applications']['jenkins']['units']['jenkins/0'][ 1649 'workload-status']['current'] = 'active' 1650 client = ModelClient(JujuData('lxd'), None, None) 1651 writes = [] 1652 with patch('utility.until_timeout', autospec=True, return_value=[1]): 1653 with patch.object(client, 'get_status', autospec=True, 1654 side_effect=[initial_status, final_status]): 1655 with patch.object(GroupReporter, '_write', autospec=True, 1656 side_effect=lambda _, s: writes.append(s)): 1657 with patch('jujupy.client.time.sleep'): 1658 client.wait_for_workloads() 1659 self.assertEqual(writes, ['waiting: jenkins/0', '\n']) 1660 1661 def test_wait_for_workload_all_unknown(self): 1662 status = Status.from_text("""\ 1663 services: 1664 jenkins: 1665 units: 1666 jenkins/0: 1667 workload-status: 1668 current: unknown 1669 subordinates: 1670 ntp/0: 1671 workload-status: 1672 current: unknown 1673 """) 1674 client = ModelClient(JujuData('lxd'), None, None) 1675 writes = [] 1676 with patch('utility.until_timeout', autospec=True, return_value=[]): 1677 with patch.object(client, 'get_status', autospec=True, 1678 return_value=status): 1679 with patch.object(GroupReporter, '_write', autospec=True, 1680 side_effect=lambda _, s: writes.append(s)): 1681 with patch('jujupy.client.time.sleep'): 1682 client.wait_for_workloads(timeout=1) 1683 self.assertEqual(writes, []) 1684 1685 def test_wait_for_workload_no_workload_status(self): 1686 status = Status.from_text("""\ 1687 services: 1688 jenkins: 1689 units: 1690 jenkins/0: 1691 agent-state: active 1692 """) 1693 client = ModelClient(JujuData('lxd'), None, None) 1694 writes = [] 1695 with patch('utility.until_timeout', autospec=True, return_value=[]): 1696 with patch.object(client, 'get_status', autospec=True, 1697 return_value=status): 1698 with patch.object(GroupReporter, '_write', autospec=True, 1699 side_effect=lambda _, s: writes.append(s)): 1700 with patch('jujupy.client.time.sleep'): 1701 client.wait_for_workloads(timeout=1) 1702 self.assertEqual(writes, []) 1703 1704 def test_list_models(self): 1705 client = ModelClient(JujuData('foo'), None, None) 1706 with patch_juju_call(client) as j_mock: 1707 client.list_models() 1708 j_mock.assert_called_once_with( 1709 'list-models', ('-c', 'foo'), include_e=False) 1710 1711 def test_get_models(self): 1712 data = """\ 1713 models: 1714 - name: foo 1715 model-uuid: aaaa 1716 owner: admin 1717 - name: bar 1718 model-uuid: bbbb 1719 owner: admin 1720 current-model: foo 1721 """ 1722 client = ModelClient(JujuData('baz'), None, None) 1723 with patch.object(client, 'get_juju_output', 1724 return_value=data) as gjo_mock: 1725 models = client.get_models() 1726 gjo_mock.assert_called_once_with( 1727 'list-models', '-c', 'baz', '--format', 'yaml', 1728 include_e=False, timeout=120) 1729 expected_models = { 1730 'models': [ 1731 {'name': 'foo', 'model-uuid': 'aaaa', 'owner': 'admin'}, 1732 {'name': 'bar', 'model-uuid': 'bbbb', 'owner': 'admin'}], 1733 'current-model': 'foo' 1734 } 1735 self.assertEqual(expected_models, models) 1736 1737 def test_iter_model_clients(self): 1738 data = """\ 1739 models: 1740 - name: foo 1741 model-uuid: aaaa 1742 owner: admin 1743 - name: bar 1744 model-uuid: bbbb 1745 owner: admin 1746 - name: baz 1747 model-uuid: bbbb 1748 owner: user1 1749 current-model: foo 1750 """ 1751 client = ModelClient(JujuData('foo', {}), None, None) 1752 with patch.object(client, 'get_juju_output', return_value=data): 1753 model_clients = list(client.iter_model_clients()) 1754 self.assertEqual(3, len(model_clients)) 1755 self.assertIs(client, model_clients[0]) 1756 self.assertEqual('admin/bar', model_clients[1].env.environment) 1757 self.assertEqual('user1/baz', model_clients[2].env.environment) 1758 1759 def test__acquire_model_client_returns_self_when_match(self): 1760 client = ModelClient(JujuData('foo', {}), None, None) 1761 1762 self.assertEqual(client._acquire_model_client('foo'), client) 1763 self.assertEqual(client._acquire_model_client('foo', None), client) 1764 1765 def test__acquire_model_client_adds_username_component(self): 1766 client = ModelClient(JujuData('foo', {}), None, None) 1767 1768 new_client = client._acquire_model_client('bar', None) 1769 self.assertEqual(new_client.model_name, 'bar') 1770 1771 new_client = client._acquire_model_client('bar', 'user1') 1772 self.assertEqual(new_client.model_name, 'user1/bar') 1773 1774 client.env.user_name = 'admin' 1775 new_client = client._acquire_model_client('baz', 'admin') 1776 self.assertEqual(new_client.model_name, 'baz') 1777 1778 def test_get_controller_model_name(self): 1779 models = { 1780 'models': [ 1781 {'name': 'controller', 'model-uuid': 'aaaa'}, 1782 {'name': 'bar', 'model-uuid': 'bbbb'}], 1783 'current-model': 'bar' 1784 } 1785 client = ModelClient(JujuData('foo'), None, None) 1786 with patch.object(client, 'get_models', 1787 return_value=models) as gm_mock: 1788 controller_name = client.get_controller_model_name() 1789 self.assertEqual(0, gm_mock.call_count) 1790 self.assertEqual('controller', controller_name) 1791 1792 def test_get_controller_model_name_without_controller(self): 1793 models = { 1794 'models': [ 1795 {'name': 'bar', 'model-uuid': 'aaaa'}, 1796 {'name': 'baz', 'model-uuid': 'bbbb'}], 1797 'current-model': 'bar' 1798 } 1799 client = ModelClient(JujuData('foo'), None, None) 1800 with patch.object(client, 'get_models', return_value=models): 1801 controller_name = client.get_controller_model_name() 1802 self.assertEqual('controller', controller_name) 1803 1804 def test_get_controller_model_name_no_models(self): 1805 client = ModelClient(JujuData('foo'), None, None) 1806 with patch.object(client, 'get_models', return_value={}): 1807 controller_name = client.get_controller_model_name() 1808 self.assertEqual('controller', controller_name) 1809 1810 def test_get_model_uuid_returns_uuid(self): 1811 model_uuid = '9ed1bde9-45c6-4d41-851d-33fdba7fa194' 1812 yaml_string = dedent("""\ 1813 foo: 1814 name: foo 1815 model-uuid: {uuid} 1816 controller-uuid: eb67e1eb-6c54-45f5-8b6a-b6243be97202 1817 owner: admin 1818 cloud: lxd 1819 region: localhost 1820 type: lxd 1821 life: alive 1822 status: 1823 current: available 1824 since: 1 minute ago 1825 users: 1826 admin: 1827 display-name: admin 1828 access: admin 1829 last-connection: just now 1830 """.format(uuid=model_uuid)) 1831 client = ModelClient(JujuData('foo'), None, None) 1832 with patch.object(client, 'get_juju_output') as m_get_juju_output: 1833 m_get_juju_output.return_value = yaml_string 1834 self.assertEqual( 1835 client.get_model_uuid(), 1836 model_uuid 1837 ) 1838 m_get_juju_output.assert_called_once_with( 1839 'show-model', '--format', 'yaml', 'foo:foo', include_e=False) 1840 1841 def test_get_controller_model_uuid_returns_uuid(self): 1842 controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202' 1843 controller_model_uuid = '1c908e10-4f07-459a-8419-bb61553a4660' 1844 yaml_string = dedent("""\ 1845 controller: 1846 name: controller 1847 model-uuid: {model} 1848 controller-uuid: {controller} 1849 controller-name: localtempveebers 1850 owner: admin 1851 cloud: lxd 1852 region: localhost 1853 type: lxd 1854 life: alive 1855 status: 1856 current: available 1857 since: 59 seconds ago 1858 users: 1859 admin: 1860 display-name: admin 1861 access: admin 1862 last-connection: just now""".format(model=controller_model_uuid, 1863 controller=controller_uuid)) 1864 client = ModelClient(JujuData('foo'), None, None) 1865 with patch.object(client, 'get_juju_output') as m_get_juju_output: 1866 m_get_juju_output.return_value = yaml_string 1867 self.assertEqual( 1868 client.get_controller_model_uuid(), 1869 controller_model_uuid 1870 ) 1871 m_get_juju_output.assert_called_once_with( 1872 'show-model', 'controller', 1873 '--format', 'yaml', include_e=False) 1874 1875 def test_get_controller_uuid_returns_uuid(self): 1876 controller_uuid = 'eb67e1eb-6c54-45f5-8b6a-b6243be97202' 1877 yaml_string = dedent("""\ 1878 foo: 1879 details: 1880 uuid: {uuid} 1881 api-endpoints: ['10.194.140.213:17070'] 1882 cloud: lxd 1883 region: localhost 1884 models: 1885 controller: 1886 uuid: {uuid} 1887 default: 1888 uuid: 772cdd39-b454-4bd5-8704-dc9aa9ff1750 1889 current-model: default 1890 account: 1891 user: admin 1892 bootstrap-config: 1893 config: 1894 cloud: lxd 1895 cloud-type: lxd 1896 region: localhost""".format(uuid=controller_uuid)) 1897 client = ModelClient(JujuData('foo'), None, None) 1898 with patch.object(client, 'get_juju_output') as m_get_juju_output: 1899 m_get_juju_output.return_value = yaml_string 1900 self.assertEqual( 1901 client.get_controller_uuid(), 1902 controller_uuid 1903 ) 1904 m_get_juju_output.assert_called_once_with( 1905 'show-controller', 'foo', '--format', 'yaml', include_e=False) 1906 1907 def test_get_controller_client(self): 1908 client = ModelClient( 1909 JujuData('foo', {'bar': 'baz'}, 'myhome'), None, None) 1910 controller_client = client.get_controller_client() 1911 controller_env = controller_client.env 1912 self.assertEqual('controller', controller_env.environment) 1913 self.assertEqual( 1914 {'bar': 'baz', 'name': 'controller'}, controller_env._config) 1915 1916 def test_list_controllers(self): 1917 client = ModelClient(JujuData('foo'), None, None) 1918 with patch_juju_call(client) as j_mock: 1919 client.list_controllers() 1920 j_mock.assert_called_once_with('list-controllers', (), include_e=False) 1921 1922 def test_get_controller_endpoint_ipv4(self): 1923 data = """\ 1924 foo: 1925 details: 1926 api-endpoints: ['10.0.0.1:17070', '10.0.0.2:17070'] 1927 """ 1928 client = ModelClient(JujuData('foo'), None, None) 1929 with patch.object(client, 'get_juju_output', 1930 return_value=data) as gjo_mock: 1931 endpoint = client.get_controller_endpoint() 1932 self.assertEqual(('10.0.0.1', '17070'), endpoint) 1933 gjo_mock.assert_called_once_with( 1934 'show-controller', 'foo', include_e=False) 1935 1936 def test_get_controller_endpoint_ipv6(self): 1937 data = """\ 1938 foo: 1939 details: 1940 api-endpoints: ['[::1]:17070', '[fe80::216:3eff:0:9dc7]:17070'] 1941 """ 1942 client = ModelClient(JujuData('foo'), None, None) 1943 with patch.object(client, 'get_juju_output', 1944 return_value=data) as gjo_mock: 1945 endpoint = client.get_controller_endpoint() 1946 self.assertEqual(('::1', '17070'), endpoint) 1947 gjo_mock.assert_called_once_with( 1948 'show-controller', 'foo', include_e=False) 1949 1950 def test_get_controller_controller_name(self): 1951 data = """\ 1952 bar: 1953 details: 1954 api-endpoints: ['[::1]:17070', '[fe80::216:3eff:0:9dc7]:17070'] 1955 """ 1956 client = ModelClient(JujuData('foo', {}), None, None) 1957 controller_client = client.get_controller_client() 1958 client.env.controller.name = 'bar' 1959 with patch.object(controller_client, 'get_juju_output', 1960 return_value=data) as gjo: 1961 endpoint = controller_client.get_controller_endpoint() 1962 gjo.assert_called_once_with('show-controller', 'bar', 1963 include_e=False) 1964 self.assertEqual(('::1', '17070'), endpoint) 1965 1966 def test_get_controller_members(self): 1967 status = Status.from_text("""\ 1968 model: controller 1969 machines: 1970 "0": 1971 dns-name: 10.0.0.0 1972 instance-id: juju-aaaa-machine-0 1973 controller-member-status: has-vote 1974 "1": 1975 dns-name: 10.0.0.1 1976 instance-id: juju-bbbb-machine-1 1977 "2": 1978 dns-name: 10.0.0.2 1979 instance-id: juju-cccc-machine-2 1980 controller-member-status: has-vote 1981 "3": 1982 dns-name: 10.0.0.3 1983 instance-id: juju-dddd-machine-3 1984 controller-member-status: has-vote 1985 """) 1986 client = ModelClient(JujuData('foo'), None, None) 1987 with patch.object(client, 'get_status', autospec=True, 1988 return_value=status): 1989 with patch.object(client, 'get_controller_endpoint', autospec=True, 1990 return_value=('10.0.0.3', '17070')) as gce_mock: 1991 with patch.object(client, 'get_controller_member_status', 1992 wraps=client.get_controller_member_status, 1993 ) as gcms_mock: 1994 members = client.get_controller_members() 1995 # Machine 1 was ignored. Machine 3 is the leader, thus first. 1996 expected = [ 1997 Machine('3', { 1998 'dns-name': '10.0.0.3', 1999 'instance-id': 'juju-dddd-machine-3', 2000 'controller-member-status': 'has-vote'}), 2001 Machine('0', { 2002 'dns-name': '10.0.0.0', 2003 'instance-id': 'juju-aaaa-machine-0', 2004 'controller-member-status': 'has-vote'}), 2005 Machine('2', { 2006 'dns-name': '10.0.0.2', 2007 'instance-id': 'juju-cccc-machine-2', 2008 'controller-member-status': 'has-vote'}), 2009 ] 2010 self.assertEqual(expected, members) 2011 gce_mock.assert_called_once_with() 2012 # get_controller_member_status must be called to ensure compatibility 2013 # with all version of Juju. 2014 self.assertEqual(4, gcms_mock.call_count) 2015 2016 def test_get_controller_members_one(self): 2017 status = Status.from_text("""\ 2018 model: controller 2019 machines: 2020 "0": 2021 dns-name: 10.0.0.0 2022 instance-id: juju-aaaa-machine-0 2023 controller-member-status: has-vote 2024 """) 2025 client = ModelClient(JujuData('foo'), None, None) 2026 with patch.object(client, 'get_status', autospec=True, 2027 return_value=status): 2028 with patch.object(client, 'get_controller_endpoint') as gce_mock: 2029 members = client.get_controller_members() 2030 # Machine 0 was the only choice, no need to find the leader. 2031 expected = [ 2032 Machine('0', { 2033 'dns-name': '10.0.0.0', 2034 'instance-id': 'juju-aaaa-machine-0', 2035 'controller-member-status': 'has-vote'}), 2036 ] 2037 self.assertEqual(expected, members) 2038 self.assertEqual(0, gce_mock.call_count) 2039 2040 def test_get_controller_leader(self): 2041 members = [ 2042 Machine('3', {}), 2043 Machine('0', {}), 2044 Machine('2', {}), 2045 ] 2046 client = ModelClient(JujuData('foo'), None, None) 2047 with patch.object(client, 'get_controller_members', autospec=True, 2048 return_value=members): 2049 leader = client.get_controller_leader() 2050 self.assertEqual(Machine('3', {}), leader) 2051 2052 def make_controller_client(self): 2053 client = ModelClient(JujuData('lxd', {'name': 'test'}), None, None) 2054 return client.get_controller_client() 2055 2056 def test_wait_for_ha(self): 2057 value = yaml.safe_dump(self.make_ha_status()).encode('ascii') 2058 client = self.make_controller_client() 2059 with patch.object(client, 'get_juju_output', 2060 return_value=value) as gjo_mock: 2061 client.wait_for_ha() 2062 gjo_mock.assert_called_once_with( 2063 'show-status', '--format', 'yaml', controller=False) 2064 2065 def test_wait_for_ha_requires_controller_client(self): 2066 client = fake_juju_client() 2067 with self.assertRaisesRegexp(ValueError, 'wait_for_ha'): 2068 client.wait_for_ha() 2069 2070 def test_wait_for_ha_no_has_vote(self): 2071 value = yaml.safe_dump( 2072 self.make_ha_status(voting='no-vote')).encode('ascii') 2073 client = self.make_controller_client() 2074 with patch.object(client, 'get_juju_output', return_value=value): 2075 writes = [] 2076 with patch('jujupy.client.until_timeout', autospec=True, 2077 return_value=[2, 1]): 2078 with patch.object(GroupReporter, '_write', autospec=True, 2079 side_effect=lambda _, s: writes.append(s)): 2080 with self.assertRaisesRegexp( 2081 Exception, 2082 'Timed out waiting for voting to be enabled.'): 2083 with patch('jujupy.client.time.sleep'): 2084 client.wait_for_ha() 2085 dots = len(writes) - 3 2086 expected = ['no-vote: 0, 1, 2', ' .'] + (['.'] * dots) + ['\n'] 2087 self.assertEqual(writes, expected) 2088 2089 def test_wait_for_ha_timeout(self): 2090 value = yaml.safe_dump({ 2091 'machines': { 2092 '0': {'controller-member-status': 'has-vote'}, 2093 '1': {'controller-member-status': 'has-vote'}, 2094 }, 2095 'services': {}, 2096 }) 2097 client = self.make_controller_client() 2098 status = client.status_class.from_text(value) 2099 with patch('jujupy.client.until_timeout', 2100 lambda x, start=None: range(0)): 2101 with patch.object(client, 'get_status', return_value=status 2102 ) as get_status_mock: 2103 with patch('jujupy.client.time.sleep'): 2104 with self.assertRaisesRegexp( 2105 StatusNotMet, 2106 'Timed out waiting for voting to be enabled.'): 2107 client.wait_for_ha() 2108 get_status_mock.assert_called_once_with() 2109 2110 def test_wait_for_ha_timeout_with_status_error(self): 2111 value = yaml.safe_dump({ 2112 'machines': { 2113 '0': {'agent-state-info': 'running'}, 2114 '1': {'agent-state-info': 'error: foo'}, 2115 }, 2116 'services': {}, 2117 }).encode('ascii') 2118 client = self.make_controller_client() 2119 with patch('jujupy.client.until_timeout', autospec=True, 2120 return_value=[2, 1]): 2121 with patch.object(client, 'get_juju_output', return_value=value): 2122 with self.assertRaisesRegexp( 2123 ErroredUnit, '1 is in state error: foo'): 2124 with patch('jujupy.client.time.sleep'): 2125 client.wait_for_ha() 2126 2127 def test_wait_for_ha_suppresses_deadline(self): 2128 with self.only_status_checks(self.make_controller_client(), 2129 self.make_ha_status()) as client: 2130 client.wait_for_ha() 2131 2132 def test_wait_for_ha_checks_deadline(self): 2133 with self.status_does_not_check(self.make_controller_client(), 2134 self.make_ha_status()) as client: 2135 with self.assertRaises(SoftDeadlineExceeded): 2136 client.wait_for_ha() 2137 2138 def test_wait_for_deploy_started(self): 2139 value = yaml.safe_dump({ 2140 'machines': { 2141 '0': {'agent-state': 'started'}, 2142 }, 2143 'applications': { 2144 'jenkins': { 2145 'units': { 2146 'jenkins/1': {'baz': 'qux'} 2147 } 2148 } 2149 } 2150 }).encode('ascii') 2151 client = ModelClient(JujuData('lxd'), None, None) 2152 with patch.object(client, 'get_juju_output', return_value=value): 2153 client.wait_for_deploy_started() 2154 2155 def test_wait_for_deploy_started_timeout(self): 2156 value = yaml.safe_dump({ 2157 'machines': { 2158 '0': {'agent-state': 'started'}, 2159 }, 2160 'applications': {}, 2161 }) 2162 client = ModelClient(JujuData('lxd'), None, None) 2163 with patch('jujupy.client.until_timeout', lambda x: range(0)): 2164 with patch.object(client, 'get_juju_output', return_value=value): 2165 with self.assertRaisesRegexp( 2166 StatusNotMet, 2167 'Timed out waiting for applications to start.'): 2168 with patch('jujupy.client.time.sleep'): 2169 client.wait_for_deploy_started() 2170 2171 def make_deployed_status(self): 2172 return { 2173 'machines': { 2174 '0': {'agent-state': 'started'}, 2175 }, 2176 'applications': { 2177 'jenkins': { 2178 'units': { 2179 'jenkins/1': {'baz': 'qux'} 2180 } 2181 } 2182 } 2183 } 2184 2185 def test_wait_for_deploy_started_suppresses_deadline(self): 2186 with self.only_status_checks( 2187 status=self.make_deployed_status()) as client: 2188 client.wait_for_deploy_started() 2189 2190 def test_wait_for_deploy_started_checks_deadline(self): 2191 with self.status_does_not_check( 2192 status=self.make_deployed_status()) as client: 2193 with self.assertRaises(SoftDeadlineExceeded): 2194 client.wait_for_deploy_started() 2195 2196 def test_wait_for_version(self): 2197 value = self.make_status_yaml('agent-version', '1.17.2', '1.17.2') 2198 client = ModelClient(JujuData('lxd'), None, None) 2199 with patch.object(client, 'get_juju_output', return_value=value): 2200 client.wait_for_version('1.17.2') 2201 2202 def test_wait_for_version_timeout(self): 2203 value = self.make_status_yaml('agent-version', '1.17.2', '1.17.1') 2204 client = ModelClient(JujuData('lxd'), None, None) 2205 writes = [] 2206 with patch('jujupy.client.until_timeout', 2207 lambda x, start=None: [x]): 2208 with patch.object(client, 'get_juju_output', return_value=value): 2209 with patch.object(GroupReporter, '_write', autospec=True, 2210 side_effect=lambda _, s: writes.append(s)): 2211 with self.assertRaisesRegexp( 2212 StatusNotMet, 'Some versions did not update'): 2213 with patch('jujupy.client.time.sleep'): 2214 client.wait_for_version('1.17.2') 2215 self.assertEqual(writes, ['1.17.1: jenkins/0', ' .', '\n']) 2216 2217 def test_wait_for_version_handles_connection_error(self): 2218 err = subprocess.CalledProcessError(2, 'foo') 2219 err.stderr = 'Unable to connect to environment' 2220 err = CannotConnectEnv(err) 2221 status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2') 2222 actions = [err, status] 2223 2224 def get_juju_output_fake(*args, **kwargs): 2225 action = actions.pop(0) 2226 if isinstance(action, Exception): 2227 raise action 2228 else: 2229 return action 2230 2231 client = ModelClient(JujuData('lxd'), None, None) 2232 with patch.object(client, 'get_juju_output', get_juju_output_fake): 2233 client.wait_for_version('1.17.2') 2234 2235 def test_wait_for_version_raises_non_connection_error(self): 2236 err = Exception('foo') 2237 status = self.make_status_yaml('agent-version', '1.17.2', '1.17.2') 2238 actions = [err, status] 2239 2240 def get_juju_output_fake(*args, **kwargs): 2241 action = actions.pop(0) 2242 if isinstance(action, Exception): 2243 raise action 2244 else: 2245 return action 2246 2247 client = ModelClient(JujuData('lxd'), None, None) 2248 with patch.object(client, 'get_juju_output', get_juju_output_fake): 2249 with self.assertRaisesRegexp(Exception, 'foo'): 2250 client.wait_for_version('1.17.2') 2251 2252 def test_wait_just_machine_0(self): 2253 value = yaml.safe_dump({ 2254 'machines': { 2255 '0': {'agent-state': 'started'}, 2256 }, 2257 }).encode('ascii') 2258 client = ModelClient(JujuData('lxd'), None, None) 2259 with patch.object(client, 'get_juju_output', return_value=value): 2260 with patch('jujupy.client.time.sleep'): 2261 client.wait_for(WaitMachineNotPresent('1'), quiet=True) 2262 2263 def test_wait_just_machine_0_timeout(self): 2264 value = yaml.safe_dump({ 2265 'machines': { 2266 '0': {'agent-state': 'started'}, 2267 '1': {'agent-state': 'started'}, 2268 }, 2269 }).encode('ascii') 2270 client = ModelClient(JujuData('lxd'), None, None) 2271 with patch.object(client, 'get_juju_output', return_value=value), \ 2272 patch('jujupy.client.until_timeout', 2273 lambda x, start=None: range(1)), \ 2274 self.assertRaisesRegexp( 2275 Exception, 2276 'Timed out waiting for machine removal 1'): 2277 with patch('jujupy.client.time.sleep'): 2278 client.wait_for(WaitMachineNotPresent('1'), quiet=True) 2279 2280 class NeverSatisfied: 2281 2282 timeout = 1234 2283 2284 already_satisfied = False 2285 2286 class NeverSatisfiedException(Exception): 2287 pass 2288 2289 def iter_blocking_state(self, ignored): 2290 yield ('global state', 'unsatisfied') 2291 2292 def do_raise(self, model_name, status): 2293 raise self.NeverSatisfiedException() 2294 2295 def test_wait_timeout(self): 2296 client = fake_juju_client() 2297 client.bootstrap() 2298 never_satisfied = self.NeverSatisfied() 2299 with self.assertRaises(never_satisfied.NeverSatisfiedException): 2300 with patch.object(client, 'status_until', return_value=iter( 2301 [Status({'machines': {}}, '')])) as mock_su: 2302 with patch('jujupy.client.time.sleep'): 2303 client.wait_for(never_satisfied, quiet=True) 2304 mock_su.assert_called_once_with(1234) 2305 2306 def test_wait_for_emits_output(self): 2307 client = fake_juju_client() 2308 client.bootstrap() 2309 mock_wait = Mock(timeout=300, already_satisfied=False) 2310 mock_wait.iter_blocking_state.side_effect = [ 2311 [('0', 'still-present')], 2312 [('0', 'still-present')], 2313 [('0', 'still-present')], 2314 [], 2315 ] 2316 writes = [] 2317 with patch.object(GroupReporter, '_write', autospec=True, 2318 side_effect=lambda _, s: writes.append(s)): 2319 with patch('jujupy.client.time.sleep'): 2320 client.wait_for(mock_wait) 2321 self.assertEqual('still-present: 0 ..\n', ''.join(writes)) 2322 2323 def test_wait_for_quiet(self): 2324 client = fake_juju_client() 2325 client.bootstrap() 2326 mock_wait = Mock(timeout=300) 2327 mock_wait.iter_blocking_state.side_effect = [ 2328 [('0', 'still-present')], 2329 [('0', 'still-present')], 2330 [('0', 'still-present')], 2331 [], 2332 ] 2333 writes = [] 2334 with patch.object(GroupReporter, '_write', autospec=True, 2335 side_effect=lambda _, s: writes.append(s)): 2336 with patch('jujupy.client.time.sleep'): 2337 client.wait_for(mock_wait, quiet=True) 2338 self.assertEqual('', ''.join(writes)) 2339 2340 def test_wait_bad_status(self): 2341 client = fake_juju_client() 2342 client.bootstrap() 2343 2344 never_satisfied = self.NeverSatisfied() 2345 bad_status = Status({'machines': {'0': {StatusItem.MACHINE: { 2346 'current': 'error' 2347 }}}}, '') 2348 with self.assertRaises(MachineError): 2349 with patch.object(client, 'status_until', lambda timeout: iter( 2350 [bad_status])): 2351 with patch('jujupy.client.time.sleep'): 2352 client.wait_for(never_satisfied, quiet=True) 2353 2354 def test_wait_bad_status_recoverable_recovered(self): 2355 client = fake_juju_client() 2356 client.bootstrap() 2357 2358 never_satisfied = self.NeverSatisfied() 2359 bad_status = Status({ 2360 'machines': {}, 2361 'applications': {'0': {StatusItem.APPLICATION: { 2362 'current': 'error' 2363 }}} 2364 }, '') 2365 good_status = Status({'machines': {}}, '') 2366 with self.assertRaises(never_satisfied.NeverSatisfiedException): 2367 with patch.object(client, 'status_until', lambda timeout: iter( 2368 [bad_status, good_status])): 2369 with patch('jujupy.client.time.sleep'): 2370 client.wait_for(never_satisfied, quiet=True) 2371 2372 def test_wait_bad_status_recoverable_timed_out(self): 2373 client = fake_juju_client() 2374 client.bootstrap() 2375 2376 never_satisfied = self.NeverSatisfied() 2377 bad_status = Status({ 2378 'machines': {}, 2379 'applications': {'0': {StatusItem.APPLICATION: { 2380 'current': 'error' 2381 }}} 2382 }, '') 2383 with self.assertRaises(AppError): 2384 with patch.object(client, 'status_until', lambda timeout: iter( 2385 [bad_status])): 2386 with patch('jujupy.client.time.sleep'): 2387 client.wait_for(never_satisfied, quiet=True) 2388 2389 def test_wait_empty_list(self): 2390 client = fake_juju_client() 2391 client.bootstrap() 2392 with patch.object(client, 'status_until', side_effect=StatusTimeout): 2393 with patch('jujupy.client.time.sleep'): 2394 self.assertEqual(client.wait_for( 2395 ConditionList([]), quiet=True).status, 2396 client.get_status().status) 2397 2398 def test_set_model_constraints(self): 2399 client = ModelClient(JujuData('bar', {}), None, '/foo') 2400 with patch_juju_call(client) as juju_mock: 2401 client.set_model_constraints({'bar': 'baz'}) 2402 juju_mock.assert_called_once_with('set-model-constraints', 2403 ('bar=baz',)) 2404 2405 def test_get_model_config(self): 2406 env = JujuData('foo', None) 2407 fake_popen = FakePopen(yaml.safe_dump({'bar': 'baz'}), None, 0) 2408 client = ModelClient(env, None, 'juju') 2409 with patch('subprocess.Popen', return_value=fake_popen) as po_mock: 2410 result = client.get_model_config() 2411 assert_juju_call( 2412 self, po_mock, client, ( 2413 'juju', '--show-log', 2414 'model-config', '-m', 'foo:foo', '--format', 'yaml')) 2415 self.assertEqual({'bar': 'baz'}, result) 2416 2417 def test_get_env_option(self): 2418 env = JujuData('foo', None) 2419 fake_popen = FakePopen('https://example.org/juju/tools', None, 0) 2420 client = ModelClient(env, None, 'juju') 2421 with patch('subprocess.Popen', return_value=fake_popen) as mock: 2422 result = client.get_env_option('tools-metadata-url') 2423 self.assertEqual( 2424 mock.call_args[0][0], 2425 ('juju', '--show-log', 'model-config', '-m', 'foo:foo', 2426 'tools-metadata-url')) 2427 self.assertEqual('https://example.org/juju/tools', result) 2428 2429 def test_set_env_option(self): 2430 env = JujuData('foo') 2431 client = ModelClient(env, None, 'juju') 2432 with patch('subprocess.check_call') as mock: 2433 client.set_env_option( 2434 'tools-metadata-url', 'https://example.org/juju/tools') 2435 environ = dict(os.environ) 2436 environ['JUJU_HOME'] = client.env.juju_home 2437 mock.assert_called_with( 2438 ('juju', '--show-log', 'model-config', '-m', 'foo:foo', 2439 'tools-metadata-url=https://example.org/juju/tools'), stderr=None) 2440 2441 def test_unset_env_option(self): 2442 env = JujuData('foo') 2443 client = ModelClient(env, None, 'juju') 2444 with patch('subprocess.check_call') as mock: 2445 client.unset_env_option('tools-metadata-url') 2446 environ = dict(os.environ) 2447 environ['JUJU_HOME'] = client.env.juju_home 2448 mock.assert_called_with( 2449 ('juju', '--show-log', 'model-config', '-m', 'foo:foo', 2450 '--reset', 'tools-metadata-url'), stderr=None) 2451 2452 def test__format_cloud_region(self): 2453 fcr = ModelClient._format_cloud_region 2454 self.assertEqual(('aws/us-east-1',), fcr('aws', 'us-east-1')) 2455 self.assertEqual(('us-east-1',), fcr(region='us-east-1')) 2456 self.assertRaises(ValueError, fcr, cloud='aws') 2457 self.assertEqual((), fcr()) 2458 2459 def test_get_model_defaults(self): 2460 data = {'some-key': {'default': 'black'}} 2461 raw_yaml = yaml.safe_dump(data) 2462 client = fake_juju_client() 2463 with patch.object(client, 'get_juju_output', autospec=True, 2464 return_value=raw_yaml) as output_mock: 2465 retval = client.get_model_defaults('some-key') 2466 self.assertEqual(data, retval) 2467 output_mock.assert_called_once_with( 2468 'model-defaults', '--format', 'yaml', 'some-key', include_e=False) 2469 2470 def test_get_model_defaults_cloud_region(self): 2471 raw_yaml = yaml.safe_dump({'some-key': {'default': 'red'}}) 2472 client = fake_juju_client() 2473 with patch.object(client, 'get_juju_output', autospec=True, 2474 return_value=raw_yaml) as output_mock: 2475 client.get_model_defaults('some-key', region='us-east-1') 2476 output_mock.assert_called_once_with( 2477 'model-defaults', '--format', 'yaml', 'us-east-1', 'some-key', 2478 include_e=False) 2479 2480 def test_set_model_defaults(self): 2481 client = fake_juju_client() 2482 with patch.object(client, 'juju', autospec=True) as juju_mock: 2483 client.set_model_defaults('some-key', 'white') 2484 juju_mock.assert_called_once_with( 2485 'model-defaults', ('some-key=white',), include_e=False) 2486 2487 def test_set_model_defaults_cloud_region(self): 2488 client = fake_juju_client() 2489 with patch.object(client, 'juju', autospec=True) as juju_mock: 2490 client.set_model_defaults('some-key', 'white', region='us-east-1') 2491 juju_mock.assert_called_once_with( 2492 'model-defaults', ('us-east-1', 'some-key=white',), 2493 include_e=False) 2494 2495 def test_unset_model_defaults(self): 2496 client = fake_juju_client() 2497 with patch.object(client, 'juju', autospec=True) as juju_mock: 2498 client.unset_model_defaults('some-key') 2499 juju_mock.assert_called_once_with( 2500 'model-defaults', ('--reset', 'some-key'), include_e=False) 2501 2502 def test_unset_model_defaults_cloud_region(self): 2503 client = fake_juju_client() 2504 with patch.object(client, 'juju', autospec=True) as juju_mock: 2505 client.unset_model_defaults('some-key', region='us-east-1') 2506 juju_mock.assert_called_once_with( 2507 'model-defaults', ('us-east-1', '--reset', 'some-key'), 2508 include_e=False) 2509 2510 def test_set_testing_agent_metadata_url(self): 2511 env = JujuData(None, {'type': 'foo'}) 2512 client = ModelClient(env, None, None) 2513 with patch.object(client, 'get_env_option') as mock_get: 2514 mock_get.return_value = 'https://example.org/juju/tools' 2515 with patch.object(client, 'set_env_option') as mock_set: 2516 client.set_testing_agent_metadata_url() 2517 mock_get.assert_called_with('agent-metadata-url') 2518 mock_set.assert_called_with( 2519 'agent-metadata-url', 2520 'https://example.org/juju/testing/tools') 2521 2522 def test_set_testing_agent_metadata_url_noop(self): 2523 env = JujuData(None, {'type': 'foo'}) 2524 client = ModelClient(env, None, None) 2525 with patch.object(client, 'get_env_option') as mock_get: 2526 mock_get.return_value = 'https://example.org/juju/testing/tools' 2527 with patch.object(client, 'set_env_option') as mock_set: 2528 client.set_testing_agent_metadata_url() 2529 mock_get.assert_called_with('agent-metadata-url',) 2530 self.assertEqual(0, mock_set.call_count) 2531 2532 def test_juju(self): 2533 env = JujuData('qux') 2534 client = ModelClient(env, None, 'juju') 2535 with patch('subprocess.check_call') as mock: 2536 client.juju('foo', ('bar', 'baz')) 2537 environ = dict(os.environ) 2538 environ['JUJU_HOME'] = client.env.juju_home 2539 mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux:qux', 2540 'bar', 'baz'), stderr=None) 2541 2542 def test_expect_returns_pexpect_spawn_object(self): 2543 env = JujuData('qux') 2544 client = ModelClient(env, None, 'juju') 2545 with patch('pexpect.spawn') as mock: 2546 process = client.expect('foo', ('bar', 'baz')) 2547 2548 self.assertIs(process, mock.return_value) 2549 mock.assert_called_once_with('juju --show-log foo -m qux:qux bar baz') 2550 2551 def test_expect_uses_provided_envvar_path(self): 2552 from pexpect import ExceptionPexpect 2553 env = JujuData('qux') 2554 client = ModelClient(env, None, 'juju') 2555 2556 with temp_dir() as empty_path: 2557 broken_envvars = dict(PATH=empty_path) 2558 self.assertRaises( 2559 ExceptionPexpect, 2560 client.expect, 2561 'ls', (), extra_env=broken_envvars, 2562 ) 2563 2564 def test_juju_env(self): 2565 env = JujuData('qux') 2566 client = ModelClient(env, None, '/foobar/baz') 2567 2568 def check_path(*args, **kwargs): 2569 self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:') 2570 with patch('subprocess.check_call', side_effect=check_path): 2571 client.juju('foo', ('bar', 'baz')) 2572 2573 def test_juju_no_check(self): 2574 env = JujuData('qux') 2575 client = ModelClient(env, None, 'juju') 2576 environ = dict(os.environ) 2577 environ['JUJU_HOME'] = client.env.juju_home 2578 with patch('subprocess.call') as mock: 2579 client.juju('foo', ('bar', 'baz'), check=False) 2580 mock.assert_called_with(('juju', '--show-log', 'foo', '-m', 'qux:qux', 2581 'bar', 'baz'), stderr=None) 2582 2583 def test_juju_no_check_env(self): 2584 env = JujuData('qux') 2585 client = ModelClient(env, None, '/foobar/baz') 2586 2587 def check_path(*args, **kwargs): 2588 self.assertRegexpMatches(os.environ['PATH'], r'/foobar\:') 2589 with patch('subprocess.call', side_effect=check_path): 2590 client.juju('foo', ('bar', 'baz'), check=False) 2591 2592 def test_juju_timeout(self): 2593 env = JujuData('qux') 2594 client = ModelClient(env, None, '/foobar/baz') 2595 with patch('subprocess.check_call') as cc_mock: 2596 client.juju('foo', ('bar', 'baz'), timeout=58) 2597 self.assertEqual(cc_mock.call_args[0][0], ( 2598 sys.executable, get_timeout_path(), '58.00', '--', 'baz', 2599 '--show-log', 'foo', '-m', 'qux:qux', 'bar', 'baz')) 2600 2601 def test_juju_juju_home(self): 2602 env = JujuData('qux') 2603 os.environ['JUJU_HOME'] = 'foo' 2604 client = ModelClient(env, None, '/foobar/baz') 2605 2606 def check_home(*args, **kwargs): 2607 self.assertEqual(os.environ['JUJU_HOME'], 'foo') 2608 yield 2609 self.assertEqual(os.environ['JUJU_HOME'], 'asdf') 2610 yield 2611 2612 with patch('subprocess.check_call', side_effect=check_home): 2613 client.juju('foo', ('bar', 'baz')) 2614 client.env.juju_home = 'asdf' 2615 client.juju('foo', ('bar', 'baz')) 2616 2617 def test_juju_extra_env(self): 2618 env = JujuData('qux') 2619 client = ModelClient(env, None, 'juju') 2620 extra_env = {'JUJU': '/juju', 'JUJU_HOME': client.env.juju_home} 2621 2622 def check_env(*args, **kwargs): 2623 self.assertEqual('/juju', os.environ['JUJU']) 2624 2625 with patch('subprocess.check_call', side_effect=check_env) as mock: 2626 client.juju('quickstart', ('bar', 'baz'), extra_env=extra_env) 2627 mock.assert_called_with( 2628 ('juju', '--show-log', 'quickstart', '-m', 'qux:qux', 2629 'bar', 'baz'), stderr=None) 2630 2631 def test_juju_backup_with_tgz(self): 2632 env = JujuData('qux') 2633 client = ModelClient(env, None, '/foobar/baz') 2634 2635 with patch( 2636 'subprocess.Popen', 2637 return_value=FakePopen('foojuju-backup-24.tgzz', '', 0), 2638 ) as popen_mock: 2639 backup_file = client.backup() 2640 self.assertEqual(backup_file, os.path.abspath('juju-backup-24.tgz')) 2641 assert_juju_call(self, popen_mock, client, ('baz', '--show-log', 2642 'create-backup', '-m', 'qux:qux')) 2643 2644 def test_juju_backup_with_tar_gz(self): 2645 env = JujuData('qux') 2646 client = ModelClient(env, None, '/foobar/baz') 2647 with patch('subprocess.Popen', 2648 return_value=FakePopen( 2649 'foojuju-backup-123-456.tar.gzbar', '', 0)): 2650 backup_file = client.backup() 2651 self.assertEqual( 2652 backup_file, os.path.abspath('juju-backup-123-456.tar.gz')) 2653 2654 def test_juju_backup_no_file(self): 2655 env = JujuData('qux') 2656 client = ModelClient(env, None, '/foobar/baz') 2657 with patch('subprocess.Popen', return_value=FakePopen('', '', 0)): 2658 with self.assertRaisesRegexp( 2659 Exception, 'The backup file was not found in output'): 2660 client.backup() 2661 2662 def test_juju_backup_wrong_file(self): 2663 env = JujuData('qux') 2664 client = ModelClient(env, None, '/foobar/baz') 2665 with patch('subprocess.Popen', 2666 return_value=FakePopen('mumu-backup-24.tgz', '', 0)): 2667 with self.assertRaisesRegexp( 2668 Exception, 'The backup file was not found in output'): 2669 client.backup() 2670 2671 def test_juju_backup_environ(self): 2672 env = JujuData('qux') 2673 client = ModelClient(env, None, '/foobar/baz') 2674 environ = client._shell_environ() 2675 2676 def side_effect(*args, **kwargs): 2677 self.assertEqual(environ, os.environ) 2678 return FakePopen('foojuju-backup-123-456.tar.gzbar', '', 0) 2679 with patch('subprocess.Popen', side_effect=side_effect): 2680 client.backup() 2681 self.assertNotEqual(environ, os.environ) 2682 2683 def test_restore_backup(self): 2684 env = JujuData('qux') 2685 client = ModelClient(env, None, '/foobar/baz') 2686 with patch_juju_call(client) as gjo_mock: 2687 client.restore_backup('quxx') 2688 gjo_mock.assert_called_once_with( 2689 'restore-backup', 2690 ('--file', 'quxx')) 2691 2692 def test_restore_backup_async(self): 2693 env = JujuData('qux') 2694 client = ModelClient(env, None, '/foobar/baz') 2695 with patch.object(client, 'juju_async') as gjo_mock: 2696 result = client.restore_backup_async('quxx') 2697 gjo_mock.assert_called_once_with('restore-backup', ( 2698 '--file', 'quxx')) 2699 self.assertIs(gjo_mock.return_value, result) 2700 2701 def test_enable_ha(self): 2702 env = JujuData('qux') 2703 client = ModelClient(env, None, '/foobar/baz') 2704 with patch.object(client, 'juju', autospec=True) as eha_mock: 2705 client.enable_ha() 2706 eha_mock.assert_called_once_with( 2707 'enable-ha', ('-n', '3', '-c', 'qux'), include_e=False) 2708 2709 def test_juju_async(self): 2710 env = JujuData('qux') 2711 client = ModelClient(env, None, '/foobar/baz') 2712 with patch('subprocess.Popen') as popen_class_mock: 2713 with client.juju_async('foo', ('bar', 'baz')) as proc: 2714 assert_juju_call( 2715 self, 2716 popen_class_mock, 2717 client, 2718 ('baz', '--show-log', 'foo', '-m', 'qux:qux', 2719 'bar', 'baz')) 2720 self.assertIs(proc, popen_class_mock.return_value) 2721 self.assertEqual(proc.wait.call_count, 0) 2722 proc.wait.return_value = 0 2723 proc.wait.assert_called_once_with() 2724 2725 def test_juju_async_failure(self): 2726 env = JujuData('qux') 2727 client = ModelClient(env, None, '/foobar/baz') 2728 with patch('subprocess.Popen') as popen_class_mock: 2729 with self.assertRaises(subprocess.CalledProcessError) as err_cxt: 2730 with client.juju_async('foo', ('bar', 'baz')): 2731 proc_mock = popen_class_mock.return_value 2732 proc_mock.wait.return_value = 23 2733 self.assertEqual(err_cxt.exception.returncode, 23) 2734 self.assertEqual(err_cxt.exception.cmd, ( 2735 'baz', '--show-log', 'foo', '-m', 'qux:qux', 'bar', 'baz')) 2736 2737 def test_juju_async_environ(self): 2738 env = JujuData('qux') 2739 client = ModelClient(env, None, '/foobar/baz') 2740 environ = client._shell_environ() 2741 proc_mock = Mock() 2742 with patch('subprocess.Popen') as popen_class_mock: 2743 2744 def check_environ(*args, **kwargs): 2745 self.assertEqual(environ, os.environ) 2746 return proc_mock 2747 popen_class_mock.side_effect = check_environ 2748 proc_mock.wait.return_value = 0 2749 with client.juju_async('foo', ('bar', 'baz')): 2750 pass 2751 self.assertNotEqual(environ, os.environ) 2752 2753 def test_get_juju_timings(self): 2754 first_start = datetime(2017, 3, 22, 23, 36, 52, 0) 2755 first_end = first_start + timedelta(seconds=2) 2756 second_start = datetime(2017, 5, 22, 23, 36, 52, 0) 2757 env = JujuData('foo') 2758 client = ModelClient(env, None, 'my/juju/bin') 2759 client._backend.juju_timings.extend([ 2760 CommandTime('command1', ['command1', 'arg1'], start=first_start), 2761 CommandTime( 2762 'command2', ['command2', 'arg1', 'arg2'], start=second_start)]) 2763 client._backend.juju_timings[0].actual_completion(end=first_end) 2764 flattened_timings = client.get_juju_timings() 2765 expected = [ 2766 { 2767 'command': 'command1', 2768 'full_args': ['command1', 'arg1'], 2769 'start': first_start, 2770 'end': first_end, 2771 'total_seconds': 2, 2772 }, 2773 { 2774 'command': 'command2', 2775 'full_args': ['command2', 'arg1', 'arg2'], 2776 'start': second_start, 2777 'end': None, 2778 'total_seconds': None, 2779 } 2780 ] 2781 self.assertEqual(flattened_timings, expected) 2782 2783 def test_deployer(self): 2784 client = ModelClient(JujuData('foo', {'type': 'lxd'}), 2785 '1.23-series-arch', None) 2786 with patch.object(ModelClient, 'juju') as mock: 2787 client.deployer('bundle:~juju-qa/some-bundle') 2788 mock.assert_called_with( 2789 'deployer', ('-e', 'foo:foo', '--debug', '--deploy-delay', 2790 '10', '--timeout', '3600', '--config', 2791 'bundle:~juju-qa/some-bundle'), 2792 include_e=False) 2793 2794 def test_deployer_with_bundle_name(self): 2795 client = ModelClient(JujuData('foo', {'type': 'lxd'}), 2796 '2.0.0-series-arch', None) 2797 with patch.object(ModelClient, 'juju') as mock: 2798 client.deployer('bundle:~juju-qa/some-bundle', 'name') 2799 mock.assert_called_with( 2800 'deployer', ('-e', 'foo:foo', '--debug', '--deploy-delay', 2801 '10', '--timeout', '3600', '--config', 2802 'bundle:~juju-qa/some-bundle', 'name'), 2803 include_e=False) 2804 2805 def test_quickstart_maas(self): 2806 client = ModelClient(JujuData(None, {'type': 'maas'}), 2807 '1.23-series-arch', '/juju') 2808 with patch.object(ModelClient, 'juju') as mock: 2809 client.quickstart('bundle:~juju-qa/some-bundle') 2810 mock.assert_called_with( 2811 'quickstart', ('--constraints', 'mem=2G', '--no-browser', 2812 'bundle:~juju-qa/some-bundle'), 2813 extra_env={'JUJU': '/juju'}) 2814 2815 def test_quickstart_local(self): 2816 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2817 '1.23-series-arch', '/juju') 2818 with patch.object(ModelClient, 'juju') as mock: 2819 client.quickstart('bundle:~juju-qa/some-bundle') 2820 mock.assert_called_with( 2821 'quickstart', ('--constraints', 'mem=2G', '--no-browser', 2822 'bundle:~juju-qa/some-bundle'), 2823 extra_env={'JUJU': '/juju'}) 2824 2825 def test_quickstart_template(self): 2826 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2827 '1.23-series-arch', '/juju') 2828 with patch.object(ModelClient, 'juju') as mock: 2829 client.quickstart('bundle:~juju-qa/some-{container}-bundle') 2830 mock.assert_called_with( 2831 'quickstart', ('--constraints', 'mem=2G', '--no-browser', 2832 'bundle:~juju-qa/some-lxd-bundle'), 2833 extra_env={'JUJU': '/juju'}) 2834 2835 def test_action_do(self): 2836 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2837 '1.23-series-arch', None) 2838 with patch.object(ModelClient, 'get_juju_output') as mock: 2839 mock.return_value = \ 2840 "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9" 2841 id = client.action_do("foo/0", "myaction", "param=5") 2842 self.assertEqual(id, "5a92ec93-d4be-4399-82dc-7431dbfd08f9") 2843 mock.assert_called_once_with( 2844 'run-action', 'foo/0', 'myaction', "param=5" 2845 ) 2846 2847 def test_action_do_error(self): 2848 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2849 '1.23-series-arch', None) 2850 with patch.object(ModelClient, 'get_juju_output') as mock: 2851 mock.return_value = "some bad text" 2852 with self.assertRaisesRegexp(Exception, 2853 "Action id not found in output"): 2854 client.action_do("foo/0", "myaction", "param=5") 2855 2856 def test_action_fetch(self): 2857 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2858 '1.23-series-arch', None) 2859 with patch.object(ModelClient, 'get_juju_output') as mock: 2860 ret = "status: completed\nfoo: bar" 2861 mock.return_value = ret 2862 out = client.action_fetch("123") 2863 self.assertEqual(out, ret) 2864 mock.assert_called_once_with( 2865 'show-action-output', '123', "--wait", "1m" 2866 ) 2867 2868 def test_action_fetch_timeout(self): 2869 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2870 '1.23-series-arch', None) 2871 ret = "status: pending\nfoo: bar" 2872 with patch.object(ModelClient, 2873 'get_juju_output', return_value=ret): 2874 with self.assertRaisesRegexp( 2875 Exception, 2876 "Timed out waiting for action to complete during fetch with " 2877 "status: pending." 2878 ): 2879 client.action_fetch("123") 2880 2881 def test_action_do_fetch(self): 2882 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2883 '1.23-series-arch', None) 2884 with patch.object(ModelClient, 'get_juju_output') as mock: 2885 ret = "status: completed\nfoo: bar" 2886 # setting side_effect to an iterable will return the next value 2887 # from the list each time the function is called. 2888 mock.side_effect = [ 2889 "Action queued with id: 5a92ec93-d4be-4399-82dc-7431dbfd08f9", 2890 ret] 2891 out = client.action_do_fetch("foo/0", "myaction", "param=5") 2892 self.assertEqual(out, ret) 2893 2894 def test_run(self): 2895 client = fake_juju_client(cls=ModelClient) 2896 run_list = [ 2897 {"MachineId": "1", 2898 "Stdout": "Linux\n", 2899 "ReturnCode": 255, 2900 "Stderr": "Permission denied (publickey,password)"}] 2901 run_output = json.dumps(run_list) 2902 with patch.object(client._backend, 'get_juju_output', 2903 return_value=run_output) as gjo_mock: 2904 result = client.run(('wname',), applications=['foo', 'bar']) 2905 self.assertEqual(run_list, result) 2906 gjo_mock.assert_called_once_with( 2907 'run', ('--format', 'json', '--application', 'foo,bar', 'wname'), 2908 frozenset(['migration']), 'foo', 2909 'name:name', user_name=None) 2910 2911 def test_run_machines(self): 2912 client = fake_juju_client(cls=ModelClient) 2913 output = json.dumps({"ReturnCode": 255}) 2914 with patch.object(client, 'get_juju_output', 2915 return_value=output) as output_mock: 2916 client.run(['true'], machines=['0', '1', '2']) 2917 output_mock.assert_called_once_with( 2918 'run', '--format', 'json', '--machine', '0,1,2', 'true') 2919 2920 def test_run_use_json_false(self): 2921 client = fake_juju_client(cls=ModelClient) 2922 output = json.dumps({"ReturnCode": 255}) 2923 with patch.object(client, 'get_juju_output', return_value=output): 2924 result = client.run(['true'], use_json=False) 2925 self.assertEqual(output, result) 2926 2927 def test_run_units(self): 2928 client = fake_juju_client(cls=ModelClient) 2929 output = json.dumps({"ReturnCode": 255}) 2930 with patch.object(client, 'get_juju_output', 2931 return_value=output) as output_mock: 2932 client.run(['true'], units=['foo/0', 'foo/1', 'foo/2']) 2933 output_mock.assert_called_once_with( 2934 'run', '--format', 'json', '--unit', 'foo/0,foo/1,foo/2', 'true') 2935 2936 def test_list_space(self): 2937 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2938 '1.23-series-arch', None) 2939 yaml_dict = {'foo': 'bar'} 2940 output = yaml.safe_dump(yaml_dict) 2941 with patch.object(client, 'get_juju_output', return_value=output, 2942 autospec=True) as gjo_mock: 2943 result = client.list_space() 2944 self.assertEqual(result, yaml_dict) 2945 gjo_mock.assert_called_once_with('list-space') 2946 2947 def test_add_space(self): 2948 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2949 '1.23-series-arch', None) 2950 with patch.object(client, 'juju', autospec=True) as juju_mock: 2951 client.add_space('foo-space') 2952 juju_mock.assert_called_once_with('add-space', ('foo-space')) 2953 2954 def test_add_subnet(self): 2955 client = ModelClient(JujuData(None, {'type': 'lxd'}), 2956 '1.23-series-arch', None) 2957 with patch.object(client, 'juju', autospec=True) as juju_mock: 2958 client.add_subnet('bar-subnet', 'foo-space') 2959 juju_mock.assert_called_once_with('add-subnet', 2960 ('bar-subnet', 'foo-space')) 2961 2962 def test__shell_environ_uses_pathsep(self): 2963 client = ModelClient(JujuData('foo'), None, 'foo/bar/juju') 2964 with patch('os.pathsep', '!'): 2965 environ = client._shell_environ() 2966 self.assertRegexpMatches(environ['PATH'], r'foo/bar\!') 2967 2968 def test_set_config(self): 2969 client = ModelClient(JujuData('bar', {}), None, '/foo') 2970 with patch_juju_call(client) as juju_mock: 2971 client.set_config('foo', {'bar': 'baz'}) 2972 juju_mock.assert_called_once_with('config', ('foo', 'bar=baz')) 2973 2974 def test_get_config(self): 2975 def output(*args, **kwargs): 2976 return yaml.safe_dump({ 2977 'charm': 'foo', 2978 'service': 'foo', 2979 'settings': { 2980 'dir': { 2981 'default': 'true', 2982 'description': 'bla bla', 2983 'type': 'string', 2984 'value': '/tmp/charm-dir', 2985 } 2986 } 2987 }) 2988 expected = yaml.safe_load(output()) 2989 client = ModelClient(JujuData('bar', {}), None, '/foo') 2990 with patch.object(client, 'get_juju_output', 2991 side_effect=output) as gjo_mock: 2992 results = client.get_config('foo') 2993 self.assertEqual(expected, results) 2994 gjo_mock.assert_called_once_with('config', 'foo') 2995 2996 def test_get_service_config(self): 2997 def output(*args, **kwargs): 2998 return yaml.safe_dump({ 2999 'charm': 'foo', 3000 'service': 'foo', 3001 'settings': { 3002 'dir': { 3003 'default': 'true', 3004 'description': 'bla bla', 3005 'type': 'string', 3006 'value': '/tmp/charm-dir', 3007 } 3008 } 3009 }) 3010 expected = yaml.safe_load(output()) 3011 client = ModelClient(JujuData('bar', {}), None, '/foo') 3012 with patch.object(client, 'get_juju_output', side_effect=output): 3013 results = client.get_service_config('foo') 3014 self.assertEqual(expected, results) 3015 3016 def test_get_service_config_timesout(self): 3017 client = ModelClient(JujuData('foo', {}), None, '/foo') 3018 with patch('jujupy.client.until_timeout', return_value=range(0)): 3019 with self.assertRaisesRegexp( 3020 Exception, 'Timed out waiting for juju get'): 3021 with patch('jujupy.client.time.sleep'): 3022 client.get_service_config('foo') 3023 3024 def test_upgrade_mongo(self): 3025 client = ModelClient(JujuData('bar', {}), None, '/foo') 3026 with patch_juju_call(client) as juju_mock: 3027 client.upgrade_mongo() 3028 juju_mock.assert_called_once_with('upgrade-mongo', ()) 3029 3030 def test_enable_feature(self): 3031 client = ModelClient(JujuData('bar', {}), None, '/foo') 3032 self.assertEqual(set(), client.feature_flags) 3033 client.enable_feature('actions') 3034 self.assertEqual(set(['actions']), client.feature_flags) 3035 3036 def test_enable_feature_invalid(self): 3037 client = ModelClient(JujuData('bar', {}), None, '/foo') 3038 self.assertEqual(set(), client.feature_flags) 3039 with self.assertRaises(ValueError) as ctx: 3040 client.enable_feature('nomongo') 3041 self.assertEqual(str(ctx.exception), "Unknown feature flag: 'nomongo'") 3042 3043 def test_is_juju1x(self): 3044 client = ModelClient(None, '1.25.5', None) 3045 self.assertTrue(client.is_juju1x()) 3046 3047 def test_is_juju1x_false(self): 3048 client = ModelClient(None, '2.0.0', None) 3049 self.assertFalse(client.is_juju1x()) 3050 3051 def test__get_register_command_returns_register_token(self): 3052 output = dedent("""\ 3053 User "x" added 3054 User "x" granted read access to model "y" 3055 Please send this command to x: 3056 juju register AaBbCc""") 3057 output_cmd = 'AaBbCc' 3058 fake_client = fake_juju_client() 3059 3060 register_cmd = fake_client._get_register_command(output) 3061 self.assertEqual(register_cmd, output_cmd) 3062 3063 def test_revoke(self): 3064 fake_client = fake_juju_client() 3065 username = 'fakeuser' 3066 model = 'foo' 3067 default_permissions = 'read' 3068 default_model = fake_client.model_name 3069 default_controller = fake_client.env.controller.name 3070 3071 with patch_juju_call(fake_client): 3072 fake_client.revoke(username) 3073 fake_client.juju.assert_called_with('revoke', 3074 ('-c', default_controller, 3075 username, default_permissions, 3076 default_model), 3077 include_e=False) 3078 3079 fake_client.revoke(username, model) 3080 fake_client.juju.assert_called_with('revoke', 3081 ('-c', default_controller, 3082 username, default_permissions, 3083 model), 3084 include_e=False) 3085 3086 fake_client.revoke(username, model, permissions='write') 3087 fake_client.juju.assert_called_with('revoke', 3088 ('-c', default_controller, 3089 username, 'write', model), 3090 include_e=False) 3091 3092 def test_add_user_perms(self): 3093 fake_client = fake_juju_client() 3094 username = 'fakeuser' 3095 3096 # Ensure add_user returns expected value. 3097 self.assertEqual( 3098 fake_client.add_user_perms(username), 3099 get_user_register_token(username)) 3100 3101 @staticmethod 3102 def assert_add_user_perms(model, permissions): 3103 fake_client = fake_juju_client() 3104 username = 'fakeuser' 3105 output = get_user_register_command_info(username) 3106 if permissions is None: 3107 permissions = 'login' 3108 expected_args = [username, '-c', fake_client.env.controller.name] 3109 with patch.object(fake_client, 'get_juju_output', 3110 return_value=output) as get_output: 3111 with patch.object(fake_client, 'juju') as mock_juju: 3112 fake_client.add_user_perms(username, model, permissions) 3113 if model is None: 3114 model = fake_client.env.environment 3115 get_output.assert_called_with( 3116 'add-user', *expected_args, include_e=False) 3117 if permissions == 'login': 3118 # Granting login is ignored, it's implicit when adding 3119 # a user. 3120 if mock_juju.call_count != 0: 3121 raise Exception( 3122 'Call count {} != 0'.format( 3123 mock_juju.call_count)) 3124 else: 3125 mock_juju.assert_called_once_with( 3126 'grant', 3127 ('fakeuser', permissions, 3128 model, 3129 '-c', fake_client.env.controller.name), 3130 include_e=False) 3131 3132 def test_assert_add_user_permissions(self): 3133 model = 'foo' 3134 permissions = 'write' 3135 3136 # Check using default model and permissions 3137 self.assert_add_user_perms(None, None) 3138 3139 # Check explicit model & default permissions 3140 self.assert_add_user_perms(model, None) 3141 3142 # Check explicit model & permissions 3143 self.assert_add_user_perms(model, permissions) 3144 3145 # Check default model & explicit permissions 3146 self.assert_add_user_perms(None, permissions) 3147 3148 def test_disable_user(self): 3149 env = JujuData('foo') 3150 username = 'fakeuser' 3151 client = ModelClient(env, None, None) 3152 with patch_juju_call(client) as mock: 3153 client.disable_user(username) 3154 mock.assert_called_with( 3155 'disable-user', ('-c', 'foo', 'fakeuser'), include_e=False) 3156 3157 def test_enable_user(self): 3158 env = JujuData('foo') 3159 username = 'fakeuser' 3160 client = ModelClient(env, None, None) 3161 with patch_juju_call(client) as mock: 3162 client.enable_user(username) 3163 mock.assert_called_with( 3164 'enable-user', ('-c', 'foo', 'fakeuser'), include_e=False) 3165 3166 def test_logout(self): 3167 env = JujuData('foo') 3168 client = ModelClient(env, None, None) 3169 with patch_juju_call(client) as mock: 3170 client.logout() 3171 mock.assert_called_with( 3172 'logout', ('-c', 'foo'), include_e=False) 3173 3174 def test_register_host(self): 3175 client = fake_juju_client() 3176 controller_state = client._backend.controller_state 3177 client.env.controller.name = 'foo-controller' 3178 self.assertNotEqual(controller_state.name, client.env.controller.name) 3179 client.register_host('host1', 'email1', 'password1') 3180 self.assertEqual(controller_state.name, client.env.controller.name) 3181 self.assertEqual(controller_state.state, 'registered') 3182 jrandom = controller_state.users['jrandom@external'] 3183 self.assertEqual(jrandom['email'], 'email1') 3184 self.assertEqual(jrandom['password'], 'password1') 3185 self.assertEqual(jrandom['2fa'], '') 3186 3187 def test_login_user(self): 3188 client = fake_juju_client() 3189 controller_state = client._backend.controller_state 3190 client.env.controller.name = 'foo-controller' 3191 client.env.user_name = 'admin' 3192 username = 'bob' 3193 password = 'password1' 3194 client.login_user(username, password) 3195 user = controller_state.users[username] 3196 self.assertEqual(user['password'], password) 3197 3198 def test_create_cloned_environment(self): 3199 fake_client = fake_juju_client() 3200 fake_client.bootstrap() 3201 # fake_client_environ = fake_client._shell_environ() 3202 controller_name = 'user_controller' 3203 3204 with temp_dir() as path: 3205 cloned = fake_client.create_cloned_environment( 3206 path, 3207 controller_name 3208 ) 3209 self.assertEqual(cloned.env.juju_home, path) 3210 self.assertItemsEqual( 3211 os.listdir(path), 3212 ['credentials.yaml', 'clouds.yaml']) 3213 self.assertIs(fake_client.__class__, type(cloned)) 3214 self.assertEqual(cloned.env.controller.name, controller_name) 3215 self.assertEqual(fake_client.env.controller.name, 'name') 3216 3217 def test_list_clouds(self): 3218 env = JujuData('foo') 3219 client = ModelClient(env, None, None) 3220 with patch.object(client, 'get_juju_output') as mock: 3221 client.list_clouds() 3222 mock.assert_called_with( 3223 'list-clouds', '--format', 'json', include_e=False) 3224 3225 def test_add_cloud_interactive_maas(self): 3226 client = fake_juju_client() 3227 clouds = {'foo': { 3228 'type': 'maas', 3229 'endpoint': 'http://bar.example.com', 3230 }} 3231 client.add_cloud_interactive('foo', clouds['foo']) 3232 self.assertEqual(client._backend.clouds, clouds) 3233 3234 def test_add_cloud_interactive_maas_invalid_endpoint(self): 3235 client = fake_juju_client() 3236 clouds = {'foo': { 3237 'type': 'maas', 3238 'endpoint': 'B' * 4000, 3239 }} 3240 with self.assertRaises(InvalidEndpoint): 3241 client.add_cloud_interactive('foo', clouds['foo']) 3242 3243 def test_add_cloud_interactive_manual(self): 3244 client = fake_juju_client() 3245 clouds = {'foo': {'type': 'manual', 'endpoint': '127.100.100.1'}} 3246 client.add_cloud_interactive('foo', clouds['foo']) 3247 self.assertEqual(client._backend.clouds, clouds) 3248 3249 def test_add_cloud_interactive_manual_invalid_endpoint(self): 3250 client = fake_juju_client() 3251 clouds = {'foo': {'type': 'manual', 'endpoint': 'B' * 4000}} 3252 with self.assertRaises(InvalidEndpoint): 3253 client.add_cloud_interactive('foo', clouds['foo']) 3254 3255 def get_openstack_clouds(self): 3256 return {'foo': { 3257 'type': 'openstack', 3258 'endpoint': 'http://bar.example.com', 3259 'auth-types': ['oauth1', 'oauth12'], 3260 'regions': { 3261 'harvey': {'endpoint': 'http://harvey.example.com'}, 3262 'steve': {'endpoint': 'http://steve.example.com'}, 3263 } 3264 }} 3265 3266 def test_add_cloud_interactive_openstack(self): 3267 client = fake_juju_client() 3268 clouds = self.get_openstack_clouds() 3269 client.add_cloud_interactive('foo', clouds['foo']) 3270 self.assertEqual(client._backend.clouds, clouds) 3271 3272 def test_add_cloud_interactive_openstack_invalid_endpoint(self): 3273 client = fake_juju_client() 3274 clouds = self.get_openstack_clouds() 3275 clouds['foo']['endpoint'] = 'B' * 4000 3276 with self.assertRaises(InvalidEndpoint): 3277 client.add_cloud_interactive('foo', clouds['foo']) 3278 3279 def test_add_cloud_interactive_openstack_invalid_region_endpoint(self): 3280 client = fake_juju_client() 3281 clouds = self.get_openstack_clouds() 3282 clouds['foo']['regions']['harvey']['endpoint'] = 'B' * 4000 3283 with self.assertRaises(InvalidEndpoint): 3284 client.add_cloud_interactive('foo', clouds['foo']) 3285 3286 def test_add_cloud_interactive_openstack_invalid_auth(self): 3287 client = fake_juju_client() 3288 clouds = self.get_openstack_clouds() 3289 clouds['foo']['auth-types'] = ['invalid', 'oauth12'] 3290 with self.assertRaises(AuthNotAccepted): 3291 client.add_cloud_interactive('foo', clouds['foo']) 3292 3293 def test_add_cloud_interactive_vsphere(self): 3294 client = fake_juju_client() 3295 clouds = {'foo': { 3296 'type': 'vsphere', 3297 'endpoint': 'http://bar.example.com', 3298 'regions': { 3299 'harvey': {}, 3300 'steve': {}, 3301 } 3302 }} 3303 client.add_cloud_interactive('foo', clouds['foo']) 3304 self.assertEqual(client._backend.clouds, clouds) 3305 3306 def test_add_cloud_interactive_vsphere_invalid_endpoint(self): 3307 client = fake_juju_client() 3308 clouds = {'foo': { 3309 'type': 'vsphere', 3310 'endpoint': 'B' * 4000, 3311 'regions': { 3312 'harvey': {}, 3313 'steve': {}, 3314 } 3315 }} 3316 with self.assertRaises(InvalidEndpoint): 3317 client.add_cloud_interactive('foo', clouds['foo']) 3318 3319 def test_add_cloud_interactive_bogus(self): 3320 client = fake_juju_client() 3321 clouds = {'foo': {'type': 'bogus'}} 3322 with self.assertRaises(TypeNotAccepted): 3323 client.add_cloud_interactive('foo', clouds['foo']) 3324 3325 def test_add_cloud_interactive_invalid_name(self): 3326 client = fake_juju_client() 3327 cloud = {'type': 'manual', 'endpoint': 'example.com'} 3328 with self.assertRaises(NameNotAccepted): 3329 client.add_cloud_interactive('invalid/name', cloud) 3330 3331 def test_show_controller(self): 3332 env = JujuData('foo') 3333 client = ModelClient(env, None, None) 3334 with patch.object(client, 'get_juju_output') as mock: 3335 client.show_controller() 3336 mock.assert_called_with( 3337 'show-controller', '--format', 'json', include_e=False) 3338 3339 def test_show_machine(self): 3340 output = """\ 3341 machines: 3342 "0": 3343 series: bionic 3344 """ 3345 env = JujuData('foo') 3346 client = ModelClient(env, None, None) 3347 with patch.object(client, 'get_juju_output', autospec=True, 3348 return_value=output) as mock: 3349 data = client.show_machine('0') 3350 mock.assert_called_once_with('show-machine', '0', '--format', 'yaml') 3351 self.assertEqual({'machines': {'0': {'series': 'bionic'}}}, data) 3352 3353 def test_ssh_keys(self): 3354 client = ModelClient(JujuData('foo'), None, None) 3355 given_output = 'ssh keys output' 3356 with patch.object(client, 'get_juju_output', autospec=True, 3357 return_value=given_output) as mock: 3358 output = client.ssh_keys() 3359 self.assertEqual(output, given_output) 3360 mock.assert_called_once_with('ssh-keys') 3361 3362 def test_ssh_keys_full(self): 3363 client = ModelClient(JujuData('foo'), None, None) 3364 given_output = 'ssh keys full output' 3365 with patch.object(client, 'get_juju_output', autospec=True, 3366 return_value=given_output) as mock: 3367 output = client.ssh_keys(full=True) 3368 self.assertEqual(output, given_output) 3369 mock.assert_called_once_with('ssh-keys', '--full') 3370 3371 def test_add_ssh_key(self): 3372 client = ModelClient(JujuData('foo'), None, None) 3373 with patch.object(client, 'get_juju_output', autospec=True, 3374 return_value='') as mock: 3375 output = client.add_ssh_key('ak', 'bk') 3376 self.assertEqual(output, '') 3377 mock.assert_called_once_with( 3378 'add-ssh-key', 'ak', 'bk', merge_stderr=True) 3379 3380 def test_remove_ssh_key(self): 3381 client = ModelClient(JujuData('foo'), None, None) 3382 with patch.object(client, 'get_juju_output', autospec=True, 3383 return_value='') as mock: 3384 output = client.remove_ssh_key('ak', 'bk') 3385 self.assertEqual(output, '') 3386 mock.assert_called_once_with( 3387 'remove-ssh-key', 'ak', 'bk', merge_stderr=True) 3388 3389 def test_import_ssh_key(self): 3390 client = ModelClient(JujuData('foo'), None, None) 3391 with patch.object(client, 'get_juju_output', autospec=True, 3392 return_value='') as mock: 3393 output = client.import_ssh_key('gh:au', 'lp:bu') 3394 self.assertEqual(output, '') 3395 mock.assert_called_once_with( 3396 'import-ssh-key', 'gh:au', 'lp:bu', merge_stderr=True) 3397 3398 def test_disable_commands_properties(self): 3399 client = ModelClient(JujuData('foo'), None, None) 3400 self.assertEqual('destroy-model', client.command_set_destroy_model) 3401 self.assertEqual('remove-object', client.command_set_remove_object) 3402 self.assertEqual('all', client.command_set_all) 3403 3404 def test_list_disabled_commands(self): 3405 client = ModelClient(JujuData('foo'), None, None) 3406 with patch.object(client, 'get_juju_output', autospec=True, 3407 return_value=dedent("""\ 3408 - command-set: destroy-model 3409 message: Lock Models 3410 - command-set: remove-object""")) as mock: 3411 output = client.list_disabled_commands() 3412 self.assertEqual([{'command-set': 'destroy-model', 3413 'message': 'Lock Models'}, 3414 {'command-set': 'remove-object'}], output) 3415 mock.assert_called_once_with('list-disabled-commands', 3416 '--format', 'yaml') 3417 3418 def test_disable_command(self): 3419 client = ModelClient(JujuData('foo'), None, None) 3420 with patch_juju_call(client) as mock: 3421 client.disable_command('all', 'message') 3422 mock.assert_called_once_with('disable-command', ('all', 'message')) 3423 3424 def test_enable_command(self): 3425 client = ModelClient(JujuData('foo'), None, None) 3426 with patch_juju_call(client) as mock: 3427 client.enable_command('all') 3428 mock.assert_called_once_with('enable-command', 'all') 3429 3430 def test_sync_tools(self): 3431 client = ModelClient(JujuData('foo'), None, None) 3432 with patch_juju_call(client) as mock: 3433 client.sync_tools() 3434 mock.assert_called_once_with('sync-tools', ()) 3435 3436 def test_sync_tools_local_dir(self): 3437 client = ModelClient(JujuData('foo'), None, None) 3438 with patch_juju_call(client) as mock: 3439 client.sync_tools('/agents') 3440 mock.assert_called_once_with('sync-tools', ('--local-dir', '/agents'), 3441 include_e=False) 3442 3443 def test_generate_tool(self): 3444 client = ModelClient(JujuData('foo'), None, None) 3445 with patch_juju_call(client) as mock: 3446 client.generate_tool('/agents') 3447 mock.assert_called_once_with('metadata', 3448 ('generate-tools', '-d', '/agents'), 3449 include_e=False) 3450 3451 def test_generate_tool_with_stream(self): 3452 client = ModelClient(JujuData('foo'), None, None) 3453 with patch_juju_call(client) as mock: 3454 client.generate_tool('/agents', "testing") 3455 mock.assert_called_once_with( 3456 'metadata', ('generate-tools', '-d', '/agents', 3457 '--stream', 'testing'), include_e=False) 3458 3459 def test_add_cloud(self): 3460 client = ModelClient(JujuData('foo'), None, None) 3461 with patch_juju_call(client) as mock: 3462 client.add_cloud('localhost', 'cfile') 3463 mock.assert_called_once_with('add-cloud', 3464 ('--replace', 'localhost', 'cfile'), 3465 include_e=False) 3466 3467 def test_switch(self): 3468 def run_switch_test(expect, model=None, controller=None): 3469 client = ModelClient(JujuData('foo'), None, None) 3470 with patch_juju_call(client) as mock: 3471 client.switch(model=model, controller=controller) 3472 mock.assert_called_once_with('switch', (expect,), include_e=False) 3473 run_switch_test('default', 'default') 3474 run_switch_test('other', controller='other') 3475 run_switch_test('other:default', 'default', 'other') 3476 3477 def test_switch_no_target(self): 3478 client = ModelClient(JujuData('foo'), None, None) 3479 self.assertRaises(ValueError, client.switch) 3480 3481 3482 @contextmanager 3483 def bootstrap_context(client=None): 3484 # Avoid unnecessary syscalls. 3485 with scoped_environ(): 3486 with temp_dir() as fake_home: 3487 os.environ['JUJU_HOME'] = fake_home 3488 yield fake_home 3489 3490 3491 class TestJujuHomePath(TestCase): 3492 3493 def test_juju_home_path(self): 3494 path = juju_home_path('/home/jrandom/foo', 'bar') 3495 self.assertEqual(path, '/home/jrandom/foo/juju-homes/bar') 3496 3497 3498 class TestGetCachePath(TestCase): 3499 3500 def test_get_cache_path(self): 3501 path = get_cache_path('/home/jrandom/foo') 3502 self.assertEqual(path, '/home/jrandom/foo/environments/cache.yaml') 3503 3504 def test_get_cache_path_models(self): 3505 path = get_cache_path('/home/jrandom/foo', models=True) 3506 self.assertEqual(path, '/home/jrandom/foo/models/cache.yaml') 3507 3508 3509 class TestMakeSafeConfig(TestCase): 3510 3511 def test_default(self): 3512 client = fake_juju_client(JujuData('foo', {'type': 'bar'}, 3513 juju_home='foo'), 3514 version='1.2-alpha3-asdf-asdf') 3515 config = make_safe_config(client) 3516 self.assertEqual({ 3517 'name': 'foo', 3518 'type': 'bar', 3519 'test-mode': True, 3520 'agent-version': '1.2-alpha3', 3521 }, config) 3522 3523 def test_bootstrap_replaces_agent_version(self): 3524 client = fake_juju_client(JujuData('foo', {'type': 'bar'}, 3525 juju_home='foo')) 3526 client.bootstrap_replaces = {'agent-version'} 3527 self.assertNotIn('agent-version', make_safe_config(client)) 3528 client.env.update_config({'agent-version': '1.23'}) 3529 self.assertNotIn('agent-version', make_safe_config(client)) 3530 3531 3532 class TestTempBootstrapEnv(FakeHomeTestCase): 3533 3534 @staticmethod 3535 def get_client(env): 3536 return ModelClient(env, '1.24-fake', 'fake-juju-path') 3537 3538 def test_no_config_mangling_side_effect(self): 3539 env = JujuData('qux', {'type': 'lxd'}) 3540 client = self.get_client(env) 3541 with bootstrap_context(client) as fake_home: 3542 with temp_bootstrap_env(fake_home, client): 3543 pass 3544 self.assertEqual(env.provider, 'lxd') 3545 3546 def test_temp_bootstrap_env_provides_dir(self): 3547 env = JujuData('qux', {'type': 'lxd'}) 3548 client = self.get_client(env) 3549 juju_home = os.path.join(self.home_dir, 'juju-homes', 'qux') 3550 3551 def side_effect(*args, **kwargs): 3552 os.mkdir(juju_home) 3553 return juju_home 3554 3555 with patch('jujupy.utility.mkdtemp', side_effect=side_effect): 3556 with temp_bootstrap_env(self.home_dir, client) as temp_home: 3557 pass 3558 self.assertEqual(temp_home, juju_home) 3559 3560 def test_temp_bootstrap_env_no_set_home(self): 3561 env = JujuData('qux', {'type': 'lxd'}) 3562 client = self.get_client(env) 3563 os.environ['JUJU_HOME'] = 'foo' 3564 os.environ['JUJU_DATA'] = 'bar' 3565 with temp_bootstrap_env(self.home_dir, client): 3566 self.assertEqual(os.environ['JUJU_HOME'], 'foo') 3567 self.assertEqual(os.environ['JUJU_DATA'], 'bar') 3568 3569 3570 class TestController(TestCase): 3571 3572 def test_controller(self): 3573 controller = Controller('ctrl') 3574 self.assertEqual('ctrl', controller.name) 3575 3576 3577 class TestJujuData(TestCase): 3578 3579 def test_init(self): 3580 controller = Mock() 3581 with temp_dir() as juju_home: 3582 juju_data = JujuData( 3583 'foo', {'enable_os_upgrade': False}, 3584 juju_home=juju_home, controller=controller, cloud_name='bar', 3585 bootstrap_to='zone=baz') 3586 self.assertEqual(juju_home, juju_data.juju_home) 3587 self.assertEqual('bar', juju_data._cloud_name) 3588 self.assertIs(controller, juju_data.controller) 3589 self.assertEqual('zone=baz', juju_data.bootstrap_to) 3590 3591 def from_cloud_region(self, provider_type, region): 3592 with temp_dir() as juju_home: 3593 data_writer = JujuData('foo', {}, juju_home) 3594 data_writer.clouds = {'clouds': {'foo': {}}} 3595 data_writer.credentials = {'credentials': {'bar': {}}} 3596 data_writer.dump_yaml(juju_home) 3597 data_reader = JujuData.from_cloud_region('bar', region, {}, { 3598 'clouds': {'bar': {'type': provider_type, 'endpoint': 'x'}}, 3599 }, juju_home) 3600 self.assertEqual(data_reader.credentials, 3601 data_writer.credentials) 3602 self.assertEqual('bar', data_reader.get_cloud()) 3603 self.assertEqual(region, data_reader.get_region()) 3604 self.assertEqual('bar', data_reader._cloud_name) 3605 3606 def test_from_cloud_region_openstack(self): 3607 self.from_cloud_region('openstack', 'baz') 3608 3609 def test_from_cloud_region_maas(self): 3610 self.from_cloud_region('maas', None) 3611 3612 def test_from_cloud_region_vsphere(self): 3613 self.from_cloud_region('vsphere', None) 3614 3615 def test_for_existing(self): 3616 with temp_dir() as juju_home: 3617 with open(get_bootstrap_config_path(juju_home), 'w') as f: 3618 yaml.safe_dump({'controllers': {'foo': { 3619 'controller-config': { 3620 'ctrl1': 'ctrl2', 3621 'duplicated1': 'duplicated2', 3622 }, 3623 'model-config': { 3624 'model1': 'model2', 3625 'type': 'provider2', 3626 'duplicated1': 'duplicated3', 3627 }, 3628 'cloud': 'cloud1', 3629 'region': 'region1', 3630 'type': 'provider1', 3631 }}}, f) 3632 data = JujuData.for_existing(juju_home, 'foo', 'bar') 3633 self.assertEqual(data.get_cloud(), 'cloud1') 3634 self.assertEqual(data.get_region(), 'region1') 3635 self.assertEqual(data.provider, 'provider1') 3636 self.assertEqual(data._config, { 3637 'model1': 'model2', 3638 'ctrl1': 'ctrl2', 3639 'duplicated1': 'duplicated3', 3640 'region': 'region1', 3641 'type': 'provider1', 3642 }) 3643 self.assertEqual(data.controller.name, 'foo') 3644 self.assertEqual(data.environment, 'bar') 3645 3646 def test_clone(self): 3647 orig = JujuData('foo', {'type': 'bar'}, 'myhome', 3648 bootstrap_to='zonea', cloud_name='cloudname') 3649 orig.credentials = {'secret': 'password'} 3650 orig.clouds = {'name': {'meta': 'data'}} 3651 orig.local = 'local1' 3652 orig.kvm = 'kvm1' 3653 orig.maas = 'maas1' 3654 orig.joyent = 'joyent1' 3655 orig.user_name = 'user1' 3656 orig.bootstrap_to = 'zonea' 3657 3658 copy = orig.clone() 3659 self.assertIs(JujuData, type(copy)) 3660 self.assertIsNot(orig, copy) 3661 self.assertEqual(copy.environment, 'foo') 3662 self.assertIsNot(orig._config, copy._config) 3663 self.assertEqual({'type': 'bar'}, copy._config) 3664 self.assertEqual('myhome', copy.juju_home) 3665 self.assertEqual('zonea', copy.bootstrap_to) 3666 self.assertIsNot(orig.credentials, copy.credentials) 3667 self.assertEqual(orig.credentials, copy.credentials) 3668 self.assertIsNot(orig.clouds, copy.clouds) 3669 self.assertEqual(orig.clouds, copy.clouds) 3670 self.assertEqual('cloudname', copy._cloud_name) 3671 self.assertEqual({'type': 'bar'}, copy._config) 3672 self.assertEqual('myhome', copy.juju_home) 3673 self.assertEqual('kvm1', copy.kvm) 3674 self.assertEqual('maas1', copy.maas) 3675 self.assertEqual('joyent1', copy.joyent) 3676 self.assertEqual('user1', copy.user_name) 3677 self.assertEqual('zonea', copy.bootstrap_to) 3678 self.assertIs(orig.controller, copy.controller) 3679 3680 def test_set_model_name(self): 3681 env = JujuData('foo', {}, juju_home='') 3682 env.set_model_name('bar') 3683 self.assertEqual(env.environment, 'bar') 3684 self.assertEqual(env.controller.name, 'bar') 3685 self.assertEqual(env.get_option('name'), 'bar') 3686 3687 def test_clone_model_name(self): 3688 orig = JujuData('foo', {'type': 'bar', 'name': 'oldname'}, 'myhome') 3689 orig.credentials = {'secret': 'password'} 3690 orig.clouds = {'name': {'meta': 'data'}} 3691 copy = orig.clone(model_name='newname') 3692 self.assertEqual('newname', copy.environment) 3693 self.assertEqual('newname', copy.get_option('name')) 3694 3695 def test_discard_option(self): 3696 env = JujuData('foo', {'type': 'foo', 'bar': 'baz'}, juju_home='') 3697 discarded = env.discard_option('bar') 3698 self.assertEqual('baz', discarded) 3699 self.assertEqual({'type': 'foo'}, env._config) 3700 3701 def test_discard_option_not_present(self): 3702 env = JujuData('foo', {'type': 'foo'}, juju_home='') 3703 discarded = env.discard_option('bar') 3704 self.assertIs(None, discarded) 3705 self.assertEqual({'type': 'foo'}, env._config) 3706 3707 def test_update_config(self): 3708 env = JujuData('foo', {'type': 'azure'}, juju_home='') 3709 env.update_config({'bar': 'baz', 'qux': 'quxx'}) 3710 self.assertEqual(env._config, { 3711 'type': 'azure', 'bar': 'baz', 'qux': 'quxx'}) 3712 3713 def test_update_config_region(self): 3714 env = JujuData('foo', {'type': 'azure'}, juju_home='') 3715 env.update_config({'region': 'foo1'}) 3716 self.assertEqual(env._config, { 3717 'type': 'azure', 'location': 'foo1'}) 3718 self.assertEqual('WARNING Using set_region to set region to "foo1".\n', 3719 self.log_stream.getvalue()) 3720 3721 def test_update_config_type(self): 3722 env = JujuData('foo', {'type': 'azure'}, juju_home='') 3723 with self.assertRaisesRegexp( 3724 ValueError, 'type cannot be set via update_config.'): 3725 env.update_config({'type': 'foo1'}) 3726 3727 def test_update_config_cloud_name(self): 3728 env = JujuData('foo', {'type': 'azure'}, juju_home='', 3729 cloud_name='steve') 3730 for endpoint_key in ['maas-server', 'auth-url', 'host']: 3731 with self.assertRaisesRegexp( 3732 ValueError, '{} cannot be changed with' 3733 ' explicit cloud name.'.format(endpoint_key)): 3734 env.update_config({endpoint_key: 'foo1'}) 3735 3736 def test_get_cloud_random_provider(self): 3737 self.assertEqual( 3738 'bar', JujuData('foo', {'type': 'bar'}, 'home').get_cloud()) 3739 3740 def test_get_cloud_ec2(self): 3741 self.assertEqual( 3742 'aws', JujuData('foo', {'type': 'ec2', 'region': 'bar'}, 3743 'home').get_cloud()) 3744 self.assertEqual( 3745 'aws-china', JujuData('foo', { 3746 'type': 'ec2', 'region': 'cn-north-1' 3747 }, 'home').get_cloud()) 3748 3749 def test_get_cloud_gce(self): 3750 self.assertEqual( 3751 'google', JujuData('foo', {'type': 'gce', 'region': 'bar'}, 3752 'home').get_cloud()) 3753 3754 def test_get_cloud_maas(self): 3755 data = JujuData('foo', {'type': 'maas', 'maas-server': 'bar'}, 'home') 3756 data.clouds = {'clouds': { 3757 'baz': {'type': 'maas', 'endpoint': 'bar'}, 3758 'qux': {'type': 'maas', 'endpoint': 'qux'}, 3759 }} 3760 self.assertEqual('baz', data.get_cloud()) 3761 3762 def test_get_cloud_maas_wrong_type(self): 3763 data = JujuData('foo', {'type': 'maas', 'maas-server': 'bar'}, 'home') 3764 data.clouds = {'clouds': { 3765 'baz': {'type': 'foo', 'endpoint': 'bar'}, 3766 }} 3767 with self.assertRaisesRegexp(LookupError, 'No such endpoint: bar'): 3768 self.assertEqual(data.get_cloud()) 3769 3770 def test_get_cloud_openstack(self): 3771 data = JujuData('foo', {'type': 'openstack', 'auth-url': 'bar'}, 3772 'home') 3773 data.clouds = {'clouds': { 3774 'baz': {'type': 'openstack', 'endpoint': 'bar'}, 3775 'qux': {'type': 'openstack', 'endpoint': 'qux'}, 3776 }} 3777 self.assertEqual('baz', data.get_cloud()) 3778 3779 def test_get_cloud_openstack_wrong_type(self): 3780 data = JujuData('foo', {'type': 'openstack', 'auth-url': 'bar'}, 3781 'home') 3782 data.clouds = {'clouds': { 3783 'baz': {'type': 'maas', 'endpoint': 'bar'}, 3784 }} 3785 with self.assertRaisesRegexp(LookupError, 'No such endpoint: bar'): 3786 data.get_cloud() 3787 3788 def test_get_cloud_vsphere(self): 3789 data = JujuData('foo', {'type': 'vsphere', 'host': 'bar'}, 3790 'home') 3791 data.clouds = {'clouds': { 3792 'baz': {'type': 'vsphere', 'endpoint': 'bar'}, 3793 'qux': {'type': 'vsphere', 'endpoint': 'qux'}, 3794 }} 3795 self.assertEqual('baz', data.get_cloud()) 3796 3797 def test_get_cloud_credentials_item(self): 3798 juju_data = JujuData('foo', {'type': 'ec2', 'region': 'foo'}, 'home') 3799 juju_data.credentials = {'credentials': { 3800 'aws': {'credentials': {'aws': True}}, 3801 'azure': {'credentials': {'azure': True}}, 3802 }} 3803 self.assertEqual(('credentials', {'aws': True}), 3804 juju_data.get_cloud_credentials_item()) 3805 3806 def test_get_cloud_credentials(self): 3807 juju_data = JujuData('foo', {'type': 'ec2', 'region': 'foo'}, 'home') 3808 juju_data.credentials = {'credentials': { 3809 'aws': {'credentials': {'aws': True}}, 3810 'azure': {'credentials': {'azure': True}}, 3811 }} 3812 self.assertEqual({'aws': True}, juju_data.get_cloud_credentials()) 3813 3814 def test_get_cloud_name_with_cloud_name(self): 3815 juju_data = JujuData('foo', {'type': 'bar'}, 'home') 3816 self.assertEqual('bar', juju_data.get_cloud()) 3817 juju_data = JujuData('foo', {'type': 'bar'}, 'home', cloud_name='baz') 3818 self.assertEqual('baz', juju_data.get_cloud()) 3819 3820 def test_set_region(self): 3821 env = JujuData('foo', {'type': 'bar'}, 'home') 3822 env.set_region('baz') 3823 self.assertEqual(env.get_option('region'), 'baz') 3824 self.assertEqual(env.get_region(), 'baz') 3825 3826 def test_set_region_no_provider(self): 3827 env = JujuData('foo', {}, 'home') 3828 env.set_region('baz') 3829 self.assertEqual(env.get_option('region'), 'baz') 3830 3831 def test_set_region_joyent(self): 3832 env = JujuData('foo', {'type': 'joyent'}, 'home') 3833 env.set_region('baz') 3834 self.assertEqual(env.get_option('sdc-url'), 3835 'https://baz.api.joyentcloud.com') 3836 self.assertEqual(env.get_region(), 'baz') 3837 3838 def test_set_region_azure(self): 3839 env = JujuData('foo', {'type': 'azure'}, 'home') 3840 env.set_region('baz') 3841 self.assertEqual(env.get_option('location'), 'baz') 3842 self.assertEqual(env.get_region(), 'baz') 3843 3844 def test_set_region_lxd(self): 3845 env = JujuData('foo', {'type': 'lxd'}, 'home') 3846 env.set_region('baz') 3847 self.assertEqual(env.get_option('region'), 'baz') 3848 3849 def test_set_region_manual(self): 3850 env = JujuData('foo', {'type': 'manual'}, 'home') 3851 env.set_region('baz') 3852 self.assertEqual(env.get_option('bootstrap-host'), 'baz') 3853 self.assertEqual(env.get_region(), 'baz') 3854 3855 def test_set_region_manual_named_cloud(self): 3856 env = JujuData('foo', {'type': 'manual'}, 'home', cloud_name='qux') 3857 env.set_region('baz') 3858 self.assertEqual(env.get_option('region'), 'baz') 3859 self.assertEqual(env.get_region(), 'baz') 3860 3861 def test_set_region_maas(self): 3862 env = JujuData('foo', {'type': 'maas'}, 'home') 3863 with self.assertRaisesRegexp(ValueError, 3864 'Only None allowed for maas.'): 3865 env.set_region('baz') 3866 env.set_region(None) 3867 self.assertIs(env.get_region(), None) 3868 3869 def test_get_region(self): 3870 self.assertEqual( 3871 'bar', JujuData( 3872 'foo', {'type': 'foo', 'region': 'bar'}, 'home').get_region()) 3873 3874 def test_get_region_old_azure(self): 3875 self.assertEqual('northeu', JujuData('foo', { 3876 'type': 'azure', 'location': 'North EU'}, 'home').get_region()) 3877 3878 def test_get_region_azure_arm(self): 3879 self.assertEqual('bar', JujuData('foo', { 3880 'type': 'azure', 'location': 'bar', 'tenant-id': 'baz'}, 3881 'home').get_region()) 3882 3883 def test_get_region_joyent(self): 3884 self.assertEqual('bar', JujuData('foo', { 3885 'type': 'joyent', 'sdc-url': 'https://bar.api.joyentcloud.com'}, 3886 'home').get_region()) 3887 3888 def test_get_region_lxd(self): 3889 self.assertEqual('localhost', JujuData( 3890 'foo', {'type': 'lxd'}, 'home').get_region()) 3891 3892 def test_get_region_lxd_specified(self): 3893 self.assertEqual('foo', JujuData( 3894 'foo', {'type': 'lxd', 'region': 'foo'}, 'home').get_region()) 3895 3896 def test_get_region_maas(self): 3897 self.assertIs(None, JujuData('foo', { 3898 'type': 'maas', 'region': 'bar', 3899 }, 'home').get_region()) 3900 3901 def test_get_region_manual(self): 3902 self.assertEqual('baz', JujuData('foo', { 3903 'type': 'manual', 'region': 'bar', 3904 'bootstrap-host': 'baz'}, 'home').get_region()) 3905 3906 def test_get_region_manual_named_cloud(self): 3907 self.assertEqual('bar', JujuData('foo', { 3908 'type': 'manual', 'region': 'bar', 3909 'bootstrap-host': 'baz'}, 'home', cloud_name='qux').get_region()) 3910 3911 def test_get_region_manual_cloud_name_manual(self): 3912 self.assertEqual('baz', JujuData('foo', { 3913 'type': 'manual', 'region': 'bar', 3914 'bootstrap-host': 'baz'}, 'home', 3915 cloud_name='manual').get_region()) 3916 3917 def test_get_region_manual_named_cloud_no_region(self): 3918 self.assertIs(None, JujuData('foo', { 3919 'type': 'manual', 'bootstrap-host': 'baz', 3920 }, 'home', cloud_name='qux').get_region()) 3921 3922 def test_dump_yaml(self): 3923 cloud_dict = {'clouds': {'foo': {}}} 3924 credential_dict = {'credential': {'bar': {}}} 3925 data = JujuData('baz', {'type': 'qux'}, 'home') 3926 data.clouds = dict(cloud_dict) 3927 data.credentials = dict(credential_dict) 3928 with temp_dir() as path: 3929 data.dump_yaml(path) 3930 self.assertItemsEqual( 3931 ['clouds.yaml', 'credentials.yaml'], os.listdir(path)) 3932 with open(os.path.join(path, 'clouds.yaml')) as f: 3933 self.assertEqual(cloud_dict, yaml.safe_load(f)) 3934 with open(os.path.join(path, 'credentials.yaml')) as f: 3935 self.assertEqual(credential_dict, yaml.safe_load(f)) 3936 3937 def test_load_yaml(self): 3938 cloud_dict = {'clouds': {'foo': {}}} 3939 credential_dict = {'credential': {'bar': {}}} 3940 with temp_dir() as path: 3941 with open(os.path.join(path, 'clouds.yaml'), 'w') as f: 3942 yaml.safe_dump(cloud_dict, f) 3943 with open(os.path.join(path, 'credentials.yaml'), 'w') as f: 3944 yaml.safe_dump(credential_dict, f) 3945 data = JujuData('baz', {'type': 'qux'}, path) 3946 data.load_yaml() 3947 3948 def test_get_option(self): 3949 env = JujuData('foo', {'type': 'azure', 'foo': 'bar'}, juju_home='') 3950 self.assertEqual(env.get_option('foo'), 'bar') 3951 self.assertIs(env.get_option('baz'), None) 3952 3953 def test_get_option_sentinel(self): 3954 env = JujuData('foo', {'type': 'azure', 'foo': 'bar'}, juju_home='') 3955 sentinel = object() 3956 self.assertIs(env.get_option('baz', sentinel), sentinel) 3957 3958 3959 class TestDescribeSubstrate(TestCase): 3960 3961 def setUp(self): 3962 super(TestDescribeSubstrate, self).setUp() 3963 # JujuData expects a JUJU_HOME or HOME env as it gets juju_home_path 3964 os.environ['HOME'] = '/tmp/jujupy-tests/' 3965 3966 def test_openstack(self): 3967 env = JujuData('foo', { 3968 'type': 'openstack', 3969 'auth-url': 'foo', 3970 }) 3971 self.assertEqual(describe_substrate(env), 'Openstack') 3972 3973 def test_canonistack(self): 3974 env = JujuData('foo', { 3975 'type': 'openstack', 3976 'auth-url': 'https://keystone.canonistack.canonical.com:443/v2.0/', 3977 }) 3978 self.assertEqual(describe_substrate(env), 'Canonistack') 3979 3980 def test_aws(self): 3981 env = JujuData('foo', { 3982 'type': 'ec2', 3983 }) 3984 self.assertEqual(describe_substrate(env), 'AWS') 3985 3986 def test_rackspace(self): 3987 env = JujuData('foo', { 3988 'type': 'rackspace', 3989 }) 3990 self.assertEqual(describe_substrate(env), 'Rackspace') 3991 3992 def test_joyent(self): 3993 env = JujuData('foo', { 3994 'type': 'joyent', 3995 }) 3996 self.assertEqual(describe_substrate(env), 'Joyent') 3997 3998 def test_azure(self): 3999 env = JujuData('foo', { 4000 'type': 'azure', 4001 }) 4002 self.assertEqual(describe_substrate(env), 'Azure') 4003 4004 def test_maas(self): 4005 env = JujuData('foo', { 4006 'type': 'maas', 4007 }) 4008 self.assertEqual(describe_substrate(env), 'MAAS') 4009 4010 def test_bar(self): 4011 env = JujuData('foo', { 4012 'type': 'bar', 4013 }) 4014 self.assertEqual(describe_substrate(env), 'bar') 4015 4016 4017 class TestGroupReporter(TestCase): 4018 4019 def test_single(self): 4020 sio = StringIO() 4021 reporter = GroupReporter(sio, "done") 4022 self.assertEqual(sio.getvalue(), "") 4023 reporter.update({"working": ["1"]}) 4024 self.assertEqual(sio.getvalue(), "working: 1") 4025 reporter.update({"done": ["1"]}) 4026 self.assertEqual(sio.getvalue(), "working: 1\n") 4027 4028 def test_single_ticks(self): 4029 sio = StringIO() 4030 reporter = GroupReporter(sio, "done") 4031 reporter.update({"working": ["1"]}) 4032 self.assertEqual(sio.getvalue(), "working: 1") 4033 reporter.update({"working": ["1"]}) 4034 self.assertEqual(sio.getvalue(), "working: 1 .") 4035 reporter.update({"working": ["1"]}) 4036 self.assertEqual(sio.getvalue(), "working: 1 ..") 4037 reporter.update({"done": ["1"]}) 4038 self.assertEqual(sio.getvalue(), "working: 1 ..\n") 4039 4040 def test_multiple_values(self): 4041 sio = StringIO() 4042 reporter = GroupReporter(sio, "done") 4043 reporter.update({"working": ["1", "2"]}) 4044 self.assertEqual(sio.getvalue(), "working: 1, 2") 4045 reporter.update({"working": ["1"], "done": ["2"]}) 4046 self.assertEqual(sio.getvalue(), "working: 1, 2\nworking: 1") 4047 reporter.update({"done": ["1", "2"]}) 4048 self.assertEqual(sio.getvalue(), "working: 1, 2\nworking: 1\n") 4049 4050 def test_multiple_groups(self): 4051 sio = StringIO() 4052 reporter = GroupReporter(sio, "done") 4053 reporter.update({"working": ["1", "2"], "starting": ["3"]}) 4054 first = "starting: 3 | working: 1, 2" 4055 self.assertEqual(sio.getvalue(), first) 4056 reporter.update({"working": ["1", "3"], "done": ["2"]}) 4057 second = "working: 1, 3" 4058 self.assertEqual(sio.getvalue(), "\n".join([first, second])) 4059 reporter.update({"done": ["1", "2", "3"]}) 4060 self.assertEqual(sio.getvalue(), "\n".join([first, second, ""])) 4061 4062 def test_finish(self): 4063 sio = StringIO() 4064 reporter = GroupReporter(sio, "done") 4065 self.assertEqual(sio.getvalue(), "") 4066 reporter.update({"working": ["1"]}) 4067 self.assertEqual(sio.getvalue(), "working: 1") 4068 reporter.finish() 4069 self.assertEqual(sio.getvalue(), "working: 1\n") 4070 4071 def test_finish_unchanged(self): 4072 sio = StringIO() 4073 reporter = GroupReporter(sio, "done") 4074 self.assertEqual(sio.getvalue(), "") 4075 reporter.finish() 4076 self.assertEqual(sio.getvalue(), "") 4077 4078 def test_wrap_to_width(self): 4079 sio = StringIO() 4080 reporter = GroupReporter(sio, "done") 4081 self.assertEqual(sio.getvalue(), "") 4082 for _ in range(150): 4083 reporter.update({"working": ["1"]}) 4084 reporter.finish() 4085 self.assertEqual(sio.getvalue(), """\ 4086 working: 1 .................................................................... 4087 ............................................................................... 4088 .. 4089 """) 4090 4091 def test_wrap_to_width_exact(self): 4092 sio = StringIO() 4093 reporter = GroupReporter(sio, "done") 4094 reporter.wrap_width = 12 4095 self.assertEqual(sio.getvalue(), "") 4096 changes = [] 4097 for _ in range(20): 4098 reporter.update({"working": ["1"]}) 4099 changes.append(sio.getvalue()) 4100 self.assertEqual(changes[::4], [ 4101 "working: 1", 4102 "working: 1 .\n...", 4103 "working: 1 .\n.......", 4104 "working: 1 .\n...........", 4105 "working: 1 .\n............\n...", 4106 ]) 4107 reporter.finish() 4108 self.assertEqual(sio.getvalue(), changes[-1] + "\n") 4109 4110 def test_wrap_to_width_overflow(self): 4111 sio = StringIO() 4112 reporter = GroupReporter(sio, "done") 4113 reporter.wrap_width = 8 4114 self.assertEqual(sio.getvalue(), "") 4115 changes = [] 4116 for _ in range(16): 4117 reporter.update({"working": ["1"]}) 4118 changes.append(sio.getvalue()) 4119 self.assertEqual(changes[::4], [ 4120 "working: 1", 4121 "working: 1\n....", 4122 "working: 1\n........", 4123 "working: 1\n........\n....", 4124 ]) 4125 reporter.finish() 4126 self.assertEqual(sio.getvalue(), changes[-1] + "\n") 4127 4128 def test_wrap_to_width_multiple_groups(self): 4129 sio = StringIO() 4130 reporter = GroupReporter(sio, "done") 4131 reporter.wrap_width = 16 4132 self.assertEqual(sio.getvalue(), "") 4133 changes = [] 4134 for _ in range(6): 4135 reporter.update({"working": ["1", "2"]}) 4136 changes.append(sio.getvalue()) 4137 for _ in range(10): 4138 reporter.update({"working": ["1"], "done": ["2"]}) 4139 changes.append(sio.getvalue()) 4140 self.assertEqual(changes[::4], [ 4141 "working: 1, 2", 4142 "working: 1, 2 ..\n..", 4143 "working: 1, 2 ..\n...\n" 4144 "working: 1 ..", 4145 "working: 1, 2 ..\n...\n" 4146 "working: 1 .....\n.", 4147 ]) 4148 reporter.finish() 4149 self.assertEqual(sio.getvalue(), changes[-1] + "\n") 4150 4151 4152 class AssessParseStateServerFromErrorTestCase(TestCase): 4153 4154 def test_parse_new_state_server_from_error(self): 4155 output = dedent(""" 4156 Waiting for address 4157 Attempting to connect to 10.0.0.202:22 4158 Attempting to connect to 1.2.3.4:22 4159 The fingerprint for the ECDSA key sent by the remote host is 4160 """) 4161 error = subprocess.CalledProcessError(1, ['foo'], output) 4162 address = parse_new_state_server_from_error(error) 4163 self.assertEqual('1.2.3.4', address) 4164 4165 def test_parse_new_state_server_from_error_output_none(self): 4166 error = subprocess.CalledProcessError(1, ['foo'], None) 4167 address = parse_new_state_server_from_error(error) 4168 self.assertIs(None, address) 4169 4170 def test_parse_new_state_server_from_error_no_output(self): 4171 address = parse_new_state_server_from_error(Exception()) 4172 self.assertIs(None, address) 4173 4174 4175 class TestGetMachineDNSName(TestCase): 4176 4177 log_level = logging.DEBUG 4178 4179 machine_0_no_addr = """\ 4180 machines: 4181 "0": 4182 instance-id: pending 4183 """ 4184 4185 machine_0_hostname = """\ 4186 machines: 4187 "0": 4188 dns-name: a-host 4189 """ 4190 4191 machine_0_ipv6 = """\ 4192 machines: 4193 "0": 4194 dns-name: 2001:db8::3 4195 """ 4196 4197 def test_gets_host(self): 4198 status = Status.from_text(self.machine_0_hostname) 4199 fake_client = Mock(spec=['status_until']) 4200 fake_client.status_until.return_value = [status] 4201 host = get_machine_dns_name(fake_client, '0') 4202 self.assertEqual(host, "a-host") 4203 fake_client.status_until.assert_called_once_with(timeout=600) 4204 self.assertEqual(self.log_stream.getvalue(), "") 4205 4206 def test_retries_for_dns_name(self): 4207 status_pending = Status.from_text(self.machine_0_no_addr) 4208 status_host = Status.from_text(self.machine_0_hostname) 4209 fake_client = Mock(spec=['status_until']) 4210 fake_client.status_until.return_value = [status_pending, status_host] 4211 host = get_machine_dns_name(fake_client, '0') 4212 self.assertEqual(host, "a-host") 4213 fake_client.status_until.assert_called_once_with(timeout=600) 4214 self.assertEqual( 4215 self.log_stream.getvalue(), 4216 "DEBUG No dns-name yet for machine 0\n") 4217 4218 def test_retries_gives_up(self): 4219 status = Status.from_text(self.machine_0_no_addr) 4220 fake_client = Mock(spec=['status_until']) 4221 fake_client.status_until.return_value = [status] * 3 4222 host = get_machine_dns_name(fake_client, '0', timeout=10) 4223 self.assertEqual(host, None) 4224 fake_client.status_until.assert_called_once_with(timeout=10) 4225 self.assertEqual( 4226 self.log_stream.getvalue(), 4227 "DEBUG No dns-name yet for machine 0\n" * 3) 4228 4229 def test_gets_ipv6(self): 4230 status = Status.from_text(self.machine_0_ipv6) 4231 fake_client = Mock(spec=['status_until']) 4232 fake_client.status_until.return_value = [status] 4233 host = get_machine_dns_name(fake_client, '0') 4234 self.assertEqual(host, "2001:db8::3") 4235 fake_client.status_until.assert_called_once_with(timeout=600) 4236 self.assertEqual( 4237 self.log_stream.getvalue(), 4238 "WARNING Selected IPv6 address for machine 0: '2001:db8::3'\n") 4239 4240 def test_gets_ipv6_unsupported(self): 4241 status = Status.from_text(self.machine_0_ipv6) 4242 fake_client = Mock(spec=['status_until']) 4243 fake_client.status_until.return_value = [status] 4244 socket_error = socket.error 4245 with patch('jujupy.utility.socket', wraps=socket) as wrapped_socket: 4246 # Must not convert socket.error into a Mock, because Mocks don't 4247 # descend from BaseException 4248 wrapped_socket.error = socket_error 4249 del wrapped_socket.inet_pton 4250 host = get_machine_dns_name(fake_client, '0') 4251 self.assertEqual(host, "2001:db8::3") 4252 fake_client.status_until.assert_called_once_with(timeout=600) 4253 self.assertEqual(self.log_stream.getvalue(), "")