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']