github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-supply-chain-master/integration/sawtooth_integration/tests/integration_tools.py (about)

     1  # Copyright 2017 Intel Corporation
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  # ------------------------------------------------------------------------------
    15  
    16  import hashlib
    17  import json
    18  import logging
    19  import os
    20  import time
    21  from base64 import b64decode
    22  from urllib.request import urlopen
    23  from urllib.error import HTTPError
    24  from urllib.error import URLError
    25  from http.client import RemoteDisconnected
    26  import requests
    27  
    28  LOGGER = logging.getLogger(__name__)
    29  
    30  WAIT = 300
    31  
    32  
    33  class RestClient:
    34      def __init__(self, url, namespace=None):
    35          self.url = url
    36          self.namespace = namespace
    37  
    38      def get_leaf(self, address, head=None):
    39          query = self._get('/state/' + address, head=head)
    40          return b64decode(query['data'])
    41  
    42      def list_state(self, namespace=None, head=None):
    43          namespace = self.namespace if namespace is None else namespace
    44  
    45          return self._get('/state', address=namespace, head=head)
    46  
    47      def get_data(self, namespace=None, head=None):
    48          namespace = self.namespace if namespace is None else namespace
    49  
    50          return [
    51              b64decode(entry['data'])
    52              for entry in self.list_state(
    53                  namespace=namespace,
    54                  head=head,
    55              )['data']
    56          ]
    57  
    58      def send_batches(self, batch_list):
    59          """Sends a list of batches to the validator.
    60  
    61          Args:
    62              batch_list (:obj:`BatchList`): the list of batches
    63  
    64          Returns:
    65              dict: the json result data, as a dict
    66          """
    67  
    68          submit_response = self._post('/batches', batch_list)
    69          return self._submit_request("{}&wait={}".format(
    70              submit_response['link'], WAIT))
    71  
    72      def block_list(self):
    73          return self._get('/blocks')
    74  
    75      def _get(self, path, **queries):
    76          code, json_result = self._submit_request(
    77              self.url + path,
    78              params=self._format_queries(queries),
    79          )
    80  
    81          # concat any additional pages of data
    82          while code == 200 and 'next' in json_result.get('paging', {}):
    83              previous_data = json_result.get('data', [])
    84              code, json_result = self._submit_request(
    85                  json_result['paging']['next'])
    86              json_result['data'] = previous_data + json_result.get('data', [])
    87  
    88          if code == 200:
    89              return json_result
    90          elif code == 404:
    91              raise Exception(
    92                  'There is no resource with the identifier "{}"'.format(
    93                      path.split('/')[-1]))
    94          else:
    95              raise Exception("({}): {}".format(code, json_result))
    96  
    97      def _post(self, path, data, **queries):
    98          if isinstance(data, bytes):
    99              headers = {'Content-Type': 'application/octet-stream'}
   100          else:
   101              data = json.dumps(data).encode()
   102              headers = {'Content-Type': 'application/json'}
   103          headers['Content-Length'] = '%d' % len(data)
   104  
   105          code, json_result = self._submit_request(
   106              self.url + path,
   107              params=self._format_queries(queries),
   108              data=data,
   109              headers=headers,
   110              method='POST')
   111  
   112          if code == 200 or code == 201 or code == 202:
   113              return json_result
   114          else:
   115              raise Exception("({}): {}".format(code, json_result))
   116  
   117      def _submit_request(self, url, params=None, data=None,
   118                          headers=None, method="GET"):
   119          """Submits the given request, and handles the errors appropriately.
   120  
   121          Args:
   122              url (str): the request to send.
   123              params (dict): params to be passed along to get/post
   124              data (bytes): the data to include in the request.
   125              headers (dict): the headers to include in the request.
   126              method (str): the method to use for the request, "POST" or "GET".
   127  
   128          Returns:
   129              tuple of (int, str): The response status code and the json parsed
   130                  body, or the error message.
   131  
   132          Raises:
   133              `Exception`: If any issues occur with the URL.
   134          """
   135          try:
   136              if method == 'POST':
   137                  result = requests.post(
   138                      url, params=params, data=data, headers=headers)
   139              elif method == 'GET':
   140                  result = requests.get(
   141                      url, params=params, data=data, headers=headers)
   142              result.raise_for_status()
   143              return (result.status_code, result.json())
   144          except requests.exceptions.HTTPError as excp:
   145              return (excp.response.status_code, excp.response.reason)
   146          except RemoteDisconnected as excp:
   147              raise Exception(excp)
   148          except requests.exceptions.ConnectionError as excp:
   149              raise Exception(
   150                  ('Unable to connect to "{}": '
   151                   'make sure URL is correct').format(self.url))
   152  
   153      @staticmethod
   154      def _format_queries(queries):
   155          queries = {k: v for k, v in queries.items() if v is not None}
   156          return queries if queries else ''
   157  
   158  
   159  class XoClient(RestClient):
   160      def __init__(self, url):
   161          super().__init__(
   162              url=url,
   163              namespace='5b7349')
   164  
   165      def decode_data(self, data):
   166          return {
   167              name: (board, state, player_1, player_2)
   168              for name, board, state, player_1, player_2 in [
   169                  game.split(',')
   170                  for game in data.decode().split('|')
   171              ]
   172          }
   173  
   174          return data.decode().split('|')
   175  
   176      def make_xo_address(self, name):
   177          return self.namespace + hashlib.sha512(name.encode()).hexdigest()[0:64]
   178  
   179      def get_game(self, name):
   180          return self.decode_data(
   181              self.get_leaf(
   182                  self.make_xo_address(name)))[name]
   183  
   184  
   185  def wait_until_status(url, status_code=200, tries=5):
   186      """Pause the program until the given url returns the required status.
   187  
   188      Args:
   189          url (str): The url to query.
   190          status_code (int, optional): The required status code. Defaults to 200.
   191          tries (int, optional): The number of attempts to request the url for
   192              the given status. Defaults to 5.
   193      Raises:
   194          AssertionError: If the status is not recieved in the given number of
   195              tries.
   196      """
   197      attempts = tries
   198      while attempts > 0:
   199          try:
   200              response = urlopen(url)
   201              if response.getcode() == status_code:
   202                  return
   203  
   204          except HTTPError as err:
   205              if err.code == status_code:
   206                  return
   207  
   208              LOGGER.debug('failed to read url: %s', str(err))
   209          except URLError as err:
   210              LOGGER.debug('failed to read url: %s', str(err))
   211  
   212          sleep_time = (tries - attempts + 1) * 2
   213          LOGGER.debug('Retrying in %s secs', sleep_time)
   214          time.sleep(sleep_time)
   215  
   216          attempts -= 1
   217  
   218      raise AssertionError(
   219          "{} is not available within {} attempts".format(url, tries))
   220  
   221  
   222  def wait_for_rest_apis(endpoints, tries=5):
   223      """Pause the program until all the given REST API endpoints are available.
   224  
   225      Args:
   226          endpoints (list of str): A list of host:port strings.
   227          tries (int, optional): The number of attempts to request the url for
   228              availability.
   229      """
   230      for endpoint in endpoints:
   231          wait_until_status(
   232              'http://{}/blocks'.format(endpoint),
   233              status_code=200,
   234              tries=tries)
   235  
   236  
   237  class SetSawtoothHome(object):
   238  
   239      def __init__(self, sawtooth_home):
   240          self._sawtooth_home = sawtooth_home
   241  
   242      def __enter__(self):
   243          os.environ['SAWTOOTH_HOME'] = self._sawtooth_home
   244          for directory in map(lambda x: os.path.join(self._sawtooth_home, x),
   245                         ['data', 'keys', 'etc', 'policy', 'logs']):
   246              if not os.path.exists(directory):
   247                  os.mkdir(directory)
   248  
   249      def __exit__(self, exc_type, exc_val, exc_tb):
   250          del os.environ['SAWTOOTH_HOME']