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

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