github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/acceptancetests/tests/test_deploy_stack.py (about)

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