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