github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-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          submit_response = self._post('/batches', batch_list)
    68          return self._submit_request("{}&wait={}".format(
    69              submit_response['link'], WAIT))
    70  
    71      def block_list(self):
    72          return self._get('/blocks')
    73  
    74      def _get(self, path, **queries):
    75          code, json_result = self._submit_request(
    76              self.url + path,
    77              params=self._format_queries(queries),
    78          )
    79  
    80          # concat any additional pages of data
    81          while code == 200 and 'next' in json_result.get('paging', {}):
    82              previous_data = json_result.get('data', [])
    83              code, json_result = self._submit_request(
    84                  json_result['paging']['next'])
    85              json_result['data'] = previous_data + json_result.get('data', [])
    86  
    87          if code == 200:
    88              return json_result
    89          elif code == 404:
    90              raise Exception(
    91                  'There is no resource with the identifier "{}"'.format(
    92                      path.split('/')[-1]))
    93          else:
    94              raise Exception("({}): {}".format(code, json_result))
    95  
    96      def _post(self, path, data, **queries):
    97          if isinstance(data, bytes):
    98              headers = {'Content-Type': 'application/octet-stream'}
    99          else:
   100              data = json.dumps(data).encode()
   101              headers = {'Content-Type': 'application/json'}
   102          headers['Content-Length'] = '%d' % len(data)
   103  
   104          code, json_result = self._submit_request(
   105              self.url + path,
   106              params=self._format_queries(queries),
   107              data=data,
   108              headers=headers,
   109              method='POST')
   110  
   111          if code == 200 or code == 201 or code == 202:
   112              return json_result
   113          else:
   114              raise Exception("({}): {}".format(code, json_result))
   115  
   116      def _submit_request(self, url, params=None, data=None,
   117                          headers=None, method="GET"):
   118          """Submits the given request, and handles the errors appropriately.
   119  
   120          Args:
   121              url (str): the request to send.
   122              params (dict): params to be passed along to get/post
   123              data (bytes): the data to include in the request.
   124              headers (dict): the headers to include in the request.
   125              method (str): the method to use for the request, "POST" or "GET".
   126  
   127          Returns:
   128              tuple of (int, str): The response status code and the json parsed
   129                  body, or the error message.
   130  
   131          Raises:
   132              `Exception`: If any issues occur with the URL.
   133          """
   134          try:
   135              if method == 'POST':
   136                  result = requests.post(
   137                      url, params=params, data=data, headers=headers)
   138              elif method == 'GET':
   139                  result = requests.get(
   140                      url, params=params, data=data, headers=headers)
   141              result.raise_for_status()
   142              return (result.status_code, result.json())
   143          except requests.exceptions.HTTPError as excp:
   144              return (excp.response.status_code, excp.response.reason)
   145          except RemoteDisconnected as excp:
   146              raise Exception(excp)
   147          except requests.exceptions.ConnectionError as excp:
   148              raise Exception(
   149                  ('Unable to connect to "{}": '
   150                   'make sure URL is correct').format(self.url))
   151  
   152      @staticmethod
   153      def _format_queries(queries):
   154          queries = {k: v for k, v in queries.items() if v is not None}
   155          return queries if queries else ''
   156  
   157  
   158  class XoClient(RestClient):
   159      def __init__(self, url):
   160          super().__init__(
   161              url=url,
   162              namespace='5b7349')
   163  
   164      def decode_data(self, data):
   165          return {
   166              name: (board, state, player_1, player_2)
   167              for name, board, state, player_1, player_2 in [
   168                  game.split(',')
   169                  for game in data.decode().split('|')
   170              ]
   171          }
   172  
   173      def make_xo_address(self, name):
   174          return self.namespace + hashlib.sha512(name.encode()).hexdigest()[0:64]
   175  
   176      def get_game(self, name):
   177          return self.decode_data(
   178              self.get_leaf(
   179                  self.make_xo_address(name)))[name]
   180  
   181  
   182  def wait_until_status(url, status_code=200):
   183      """Pause the program until the given url returns the required status.
   184  
   185      Args:
   186          url (str): The url to query.
   187          status_code (int, optional): The required status code. Defaults to 200.
   188      """
   189      sleep_time = 1
   190      while True:
   191          try:
   192              response = urlopen(url)
   193              if response.getcode() == status_code:
   194                  return
   195  
   196          except HTTPError as err:
   197              if err.code == status_code:
   198                  return
   199  
   200              LOGGER.debug('failed to read url: %s', str(err))
   201          except URLError as err:
   202              LOGGER.debug('failed to read url: %s', str(err))
   203  
   204          LOGGER.debug('Retrying in %s secs', sleep_time)
   205          time.sleep(sleep_time)
   206  
   207  
   208  def wait_for_rest_apis(endpoints):
   209      """Pause the program until all the given REST API endpoints are available.
   210  
   211      Args:
   212          endpoints (list of str): A list of host:port strings.
   213      """
   214      for endpoint in endpoints:
   215          http = 'http://'
   216          url = endpoint if endpoint.startswith(http) else http + endpoint
   217          wait_until_status(
   218              '{}/blocks'.format(url),
   219              status_code=200)
   220  
   221  
   222  class SetSawtoothHome(object):
   223      def __init__(self, sawtooth_home):
   224          self._sawtooth_home = sawtooth_home
   225  
   226      def __enter__(self):
   227          os.environ['SAWTOOTH_HOME'] = self._sawtooth_home
   228          for directory in map(lambda x: os.path.join(self._sawtooth_home, x),
   229                               ['data', 'keys', 'etc', 'policy', 'logs']):
   230              if not os.path.exists(directory):
   231                  os.mkdir(directory)
   232  
   233      def __exit__(self, exc_type, exc_val, exc_tb):
   234          del os.environ['SAWTOOTH_HOME']