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

     1  #!/usr/bin/env python
     2  """
     3  This tests the wallet commands utilized for commercial charm billing.
     4  These commands are linked to a ubuntu sso account, and as such, require the
     5  user account to be setup before test execution (including authentication).
     6  You can use charm login to do this, or let juju authenticate with a browser.
     7  """
     8  
     9  from __future__ import print_function
    10  
    11  import argparse
    12  from fixtures import EnvironmentVariable
    13  import json
    14  import logging
    15  import os
    16  import pexpect
    17  from random import randint
    18  import shutil
    19  import subprocess
    20  import sys
    21  
    22  
    23  from deploy_stack import (
    24      BootstrapManager,
    25      )
    26  
    27  from utility import (
    28      add_basic_testing_arguments,
    29      temp_dir,
    30      JujuAssertionError,
    31      configure_logging,
    32  )
    33  
    34  __metaclass__ = type
    35  
    36  
    37  log = logging.getLogger("assess_wallet")
    38  
    39  
    40  def _get_new_wallet_limit(client):
    41      """Return available limit for new wallet"""
    42      wallets = json.loads(list_wallets(client))
    43      limit = int(wallets['total']['limit'])
    44      credit = int(wallets['credit'])
    45      log.debug('Found credit limit {}, currently used {}'.format(
    46          credit, limit))
    47      return credit - limit
    48  
    49  
    50  def _get_wallets(client):
    51      return json.loads(list_wallets(client))['wallets']
    52  
    53  
    54  def _set_wallet_value_expectations(expected_wallets, name, value):
    55      # Update our expectations accordingly
    56      for wallet in expected_wallets:
    57          if wallet['wallet'] == name:
    58              # For now, we assume we aren't spending down the wallet
    59              wallet['limit'] = value
    60              wallet['unallocated'] = value
    61              # .00 is appended to availible for some reason
    62              wallet['available'] = '{:.2f}'.format(float(value))
    63              log.info('Expected wallet updated: "{}" to {}'.format(name, value))
    64  
    65  
    66  def _try_setting_wallet(client, name, value):
    67      try:
    68          output = set_wallet(client, name, value)
    69      except subprocess.CalledProcessError as e:
    70          output = [e.output, getattr(e, 'stderr', '')]
    71          raise JujuAssertionError('Could not set wallet {}'.format(output))
    72  
    73      if 'wallet limit updated' not in output:
    74          raise JujuAssertionError('Error calling set-wallet {}'.format(output))
    75  
    76  
    77  def _try_creating_wallet(client, name, value):
    78      try:
    79          create_wallet(client, name, value)
    80          log.info('Created new wallet "{}" with value {}'.format(name,
    81                                                                  value))
    82      except subprocess.CalledProcessError as e:
    83          output = [e.output, getattr(e, 'stderr', '')]
    84          if any('already exists' in message for message in output):
    85              log.info('Reusing wallet "{}" with value {}'.format(name, value))
    86              pass  # this will be a failure once lp:1663258 is fixed
    87          else:
    88              raise JujuAssertionError(
    89                  'Error testing create-wallet: {}'.format(output))
    90      except:
    91          raise JujuAssertionError('Added duplicate wallet')
    92  
    93  
    94  def _try_greater_than_limit_wallet(client, name, limit):
    95      error_strings = {
    96          'pass': 'exceed the credit limit',
    97          'unknown': 'Error testing wallet greater than credit limit',
    98          'fail': 'Credit limit exceeded'
    99      }
   100      over_limit_value = str(limit + randint(1, 100))
   101      assert_set_wallet(client, name, over_limit_value, error_strings)
   102  
   103  
   104  def _try_negative_wallet(client, name):
   105      error_strings = {
   106          'pass': 'Could not set wallet',
   107          'unknown': 'Error testing negative wallet',
   108          'fail': 'Negative wallet allowed'
   109      }
   110      negative_wallet_value = str(randint(-1000, -1))
   111      assert_set_wallet(client, name, negative_wallet_value, error_strings)
   112  
   113  
   114  def assert_sorted_equal(found, expected):
   115      found = sorted(found)
   116      expected = sorted(expected)
   117      if found != expected:
   118          raise JujuAssertionError(
   119              'Found: {}\nExpected: {}'.format(found, expected))
   120  
   121  
   122  def assert_set_wallet(client, name, limit, error_strings):
   123      try:
   124          _try_setting_wallet(client, name, limit)
   125      except JujuAssertionError as e:
   126          if error_strings['pass'] not in e.message:
   127              raise JujuAssertionError(
   128                  '{}: {}'.format(error_strings['unknown'], e))
   129      else:
   130          raise JujuAssertionError(error_strings['fail'])
   131  
   132  
   133  def create_wallet(client, name, value):
   134      """Create a wallet"""
   135      return client.get_juju_output('create-wallet', name, value,
   136                                    include_e=False)
   137  
   138  
   139  def list_wallets(client):
   140      """Return defined wallets as json."""
   141      return client.get_juju_output('list-wallets', '--format', 'json',
   142                                    include_e=False)
   143  
   144  
   145  def set_wallet(client, name, value):
   146      """Change an existing wallet's allocation."""
   147      return client.get_juju_output('set-wallet', name, value, include_e=False)
   148  
   149  
   150  def show_wallet(client, name):
   151      """Return specified wallet as json."""
   152      return client.get_juju_output('show-wallet', name, '--format', 'json',
   153                                    include_e=False)
   154  
   155  
   156  def assess_wallet(client):
   157      # Since we can't remove wallets until lp:1663258
   158      # is fixed, we avoid creating new random wallets and hardcode.
   159      # We also, zero out the previous wallet
   160      wallet_name = 'personal'
   161      _try_setting_wallet(client, wallet_name, '0')
   162  
   163      wallet_limit = _get_new_wallet_limit(client)
   164      assess_wallet_limit(wallet_limit)
   165  
   166      expected_wallets = _get_wallets(client)
   167      wallet_value = str(randint(1, wallet_limit / 2))
   168      assess_create_wallet(client, wallet_name, wallet_value, wallet_limit)
   169  
   170      wallet_value = str(randint(wallet_limit / 2 + 1, wallet_limit))
   171      assess_set_wallet(client, wallet_name, wallet_value, wallet_limit)
   172      assess_show_wallet(client, wallet_name, wallet_value)
   173  
   174      _set_wallet_value_expectations(expected_wallets, wallet_name, wallet_value)
   175      assess_list_wallets(client, expected_wallets)
   176  
   177  
   178  def assess_wallet_limit(wallet_limit):
   179      log.info('Assessing wallet limit {}'.format(wallet_limit))
   180  
   181      if wallet_limit < 0:
   182          raise JujuAssertionError(
   183              'Negative Wallet Limit {}'.format(wallet_limit))
   184  
   185  
   186  def assess_create_wallet(client, wallet_name, wallet_value, wallet_limit):
   187      """Test create-wallet command"""
   188      log.info('create-wallet "{}" with value {}, limit {}'.format(wallet_name,
   189                                                                   wallet_value,
   190                                                                   wallet_limit))
   191  
   192      # Do this twice, to ensure wallet exists and we can check for
   193      # duplicate message. Ideally, once lp:1663258 is fixed, we will
   194      # assert on initial wallet creation as well.
   195      _try_creating_wallet(client, wallet_name, wallet_value)
   196  
   197      log.info('Trying duplicate create-wallet')
   198      _try_creating_wallet(client, wallet_name, wallet_value)
   199  
   200  
   201  def assess_list_wallets(client, expected_wallets):
   202      log.info('list-wallets testing expected values')
   203      # Since we can't remove wallets until lp:1663258
   204      # is fixed, we don't modify the list contents or count
   205      # Nonetheless, we assert on it for future use
   206      wallets = _get_wallets(client)
   207      assert_sorted_equal(wallets, expected_wallets)
   208  
   209  
   210  def assess_set_wallet(client, wallet_name, wallet_value, wallet_limit):
   211      """Test set-wallet command"""
   212      log.info('set-wallet "{}" with value {}, limit {}'.format(wallet_name,
   213                                                                wallet_value,
   214                                                                wallet_limit))
   215      _try_setting_wallet(client, wallet_name, wallet_value)
   216  
   217      # Check some bounds
   218      # Since walletting is important, and the functional test is cheap,
   219      # let's test some basic bounds
   220      log.info('Trying set-wallet with value greater than wallet limit')
   221      _try_greater_than_limit_wallet(client, wallet_name, wallet_limit)
   222  
   223      log.info('Trying set-wallet with negative value')
   224      _try_negative_wallet(client, wallet_name)
   225  
   226  
   227  def assess_show_wallet(client, wallet_name, wallet_value):
   228      log.info('show-wallet "{}" with value {}'.format(wallet_name,
   229                                                       wallet_value))
   230  
   231      wallet = json.loads(show_wallet(client, wallet_name))
   232  
   233      # assert wallet value
   234      if wallet['limit'] != wallet_value:
   235          raise JujuAssertionError('Wallet limit found {}, expected {}'.format(
   236              wallet['limit'], wallet_value))
   237  
   238      # assert on usage (0% until we use it)
   239      if wallet['total']['usage'] != '0%':
   240          raise JujuAssertionError('Wallet usage found {}, expected {}'.format(
   241              wallet['total']['usage'], '0%'))
   242  
   243  
   244  def parse_args(argv):
   245      """Parse all arguments."""
   246      parser = argparse.ArgumentParser(description="Test wallet commands")
   247      # Set to false it it's possible to overwrite actual cookie data if someone
   248      # runs it against an existing environment
   249      add_basic_testing_arguments(parser, existing=False)
   250      return parser.parse_args(argv)
   251  
   252  
   253  def set_controller_cookie_file(client):
   254      """Plant pre-generated cookie file to avoid launching Browser.
   255  
   256      Using an existing usso token use 'charm login' to create a .go-cookies file
   257      (in a tmp HOME). Copy this new cookies file to become the controller cookie
   258      file.
   259      """
   260  
   261      with temp_dir() as tmp_home:
   262          with EnvironmentVariable('HOME', tmp_home):
   263              move_usso_token_to_juju_home(tmp_home)
   264  
   265              # charm login shouldn't be interactive, fail if it is.
   266              try:
   267                  command = pexpect.spawn(
   268                      'charm', ['login'], env={'HOME': tmp_home})
   269                  command.expect(pexpect.EOF)
   270              except (pexpect).TIMEOUT:
   271                  raise RuntimeError('charm login command was interactive.')
   272  
   273              go_cookie = os.path.join(tmp_home, '.go-cookies')
   274              controller_cookie_path = os.path.join(
   275                  client.env.juju_home,
   276                  'cookies',
   277                  '{}.json'.format(client.env.controller.name))
   278  
   279              shutil.copyfile(go_cookie, controller_cookie_path)
   280  
   281  
   282  def move_usso_token_to_juju_home(tmp_home):
   283      """Move pre-packaged token to juju data dir.
   284  
   285      Move the stored store-usso-token to a tmp juju home dir for charm command
   286      use.
   287      """
   288      source_usso_path = os.path.join(
   289          os.environ['JUJU_HOME'], 'juju-bot-store-usso-token')
   290      dest_usso_dir = os.path.join(tmp_home, '.local', 'share', 'juju')
   291      os.makedirs(dest_usso_dir)
   292      dest_usso_path = os.path.join(dest_usso_dir, 'store-usso-token')
   293      shutil.copyfile(source_usso_path, dest_usso_path)
   294  
   295  
   296  def main(argv=None):
   297      args = parse_args(argv)
   298      configure_logging(args.verbose)
   299      bs_manager = BootstrapManager.from_args(args)
   300      with bs_manager.booted_context(args.upload_tools):
   301          set_controller_cookie_file(bs_manager.client)
   302          assess_wallet(bs_manager.client)
   303      return 0
   304  
   305  
   306  if __name__ == '__main__':
   307      sys.exit(main())