def exponentialBackoff(request: HttpRequest, time: int = 0, tries: int = 0, max_tries: int = 8): """Periodically retry a failed request over an increasing amount of time to handle errors related to rate limits, network volume, or response time. https://developers.google.com/drive/api/v3/handle-errors """ try: response = None while response is None: status, response = request.next_chunk() if status: print( f'Uploading: {round(status.resumable_progress / status.total_size * 100, 2)}%' ) file = request.execute(num_retries=2) return file except HttpError as e: print(e) if tries < max_tries: print(f'Trying again in {time}') t = Timer(time, exponentialBackoff, args=(request, 2**(tries + 1) + random.random(), tries + 1, max_tries)) t.start() else: raise Exception('Max tries reached.')
def createSnapshot(self, project, zone, disk, body): if disk == valid_disk_name: http = HttpMock( 'tests/data/gcp/disks.createSnapshot.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'POST' return HttpRequest( http, model.response, uri, method=method, headers={} ) elif disk == invalid_disk_name: http = HttpMock( 'tests/data/gcp/disks.createSnapshot1.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'POST' return HttpRequest( http, model.response, uri, method=method, headers={} )
def test_http_request_to_from_json(self): def _postproc(*kwargs): pass http = httplib2.Http() media_upload = MediaFileUpload( datafile('small.png'), chunksize=500, resumable=True) req = HttpRequest( http, _postproc, 'http://example.com', method='POST', body='{}', headers={'content-type': 'multipart/related; boundary="---flubber"'}, methodId='foo', resumable=media_upload) json = req.to_json() new_req = HttpRequest.from_json(json, http, _postproc) self.assertEqual({'content-type': 'multipart/related; boundary="---flubber"'}, new_req.headers) self.assertEqual('http://example.com', new_req.uri) self.assertEqual('{}', new_req.body) self.assertEqual(http, new_req.http) self.assertEqual(media_upload.to_json(), new_req.resumable.to_json()) self.assertEqual(random.random, new_req._rand) self.assertEqual(time.sleep, new_req._sleep)
def test_media_io_base_next_chunk_no_retry_403_not_configured(self): fd = BytesIO(b"i am png") upload = MediaIoBaseUpload( fd=fd, mimetype='image/png', chunksize=500, resumable=True) http = HttpMockSequence([ ({'status': '403'}, NOT_CONFIGURED_RESPONSE), ({'status': '200'}, '{}') ]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar' method = u'POST' request = HttpRequest( http, model.response, uri, method=method, headers={}, resumable=upload) request._rand = lambda: 1.0 request._sleep = mock.MagicMock() with self.assertRaises(HttpError): request.execute(num_retries=3) request._sleep.assert_not_called()
def test_retry(self): num_retries = 5 resp_seq = [({'status': '500'}, '')] * num_retries resp_seq.append(({'status': '200'}, '{}')) http = HttpMockSequence(resp_seq) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest( http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) sleeptimes = [] request._sleep = lambda x: sleeptimes.append(x) request._rand = lambda: 10 request.execute(num_retries=num_retries) self.assertEqual(num_retries, len(sleeptimes)) for retry_num in xrange(num_retries): self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
def test_turn_get_into_post(self): def _postproc(resp, content): return content http = HttpMockSequence([ ({ 'status': '200' }, 'echo_request_body'), ({ 'status': '200' }, 'echo_request_headers'), ]) # Send a long query parameter. query = {'q': 'a' * MAX_URI_LENGTH + '?&'} req = HttpRequest(http, _postproc, 'http://example.com?' + urlencode(query), method='GET', body=None, headers={}, methodId='foo', resumable=None) # Query parameters should be sent in the body. response = req.execute() self.assertEqual(b'q=' + b'a' * MAX_URI_LENGTH + b'%3F%26', response) # Extra headers should be set. response = req.execute() self.assertEqual('GET', response['x-http-method-override']) self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length']) self.assertEqual('application/x-www-form-urlencoded', response['content-type'])
def insert(self, project, zone, body): if body['sourceSnapshot'] == 'global/snapshots/{}'.format( valid_snapshot_name): http = HttpMock( 'tests/data/gcp/disks.insert.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'POST' return HttpRequest( http, model.response, uri, method=method, headers={} ) elif body['sourceSnapshot'] == 'global/snapshots/{}'.format( invalid_snapshot_name): http = HttpMock( 'tests/data/gcp/disks.insert1.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'POST' return HttpRequest( http, model.response, uri, method=method, headers={} )
def test_http_request_to_from_json(self): def _postproc(*kwargs): pass http = httplib2.Http() media_upload = MediaFileUpload(datafile('small.png'), chunksize=500, resumable=True) req = HttpRequest(http, _postproc, 'http://example.com', method='POST', body='{}', headers={ 'content-type': 'multipart/related; boundary="---flubber"' }, methodId='foo', resumable=media_upload) json = req.to_json() new_req = HttpRequest.from_json(json, http, _postproc) self.assertEqual( {'content-type': 'multipart/related; boundary="---flubber"'}, new_req.headers) self.assertEqual('http://example.com', new_req.uri) self.assertEqual('{}', new_req.body) self.assertEqual(http, new_req.http) self.assertEqual(media_upload.to_json(), new_req.resumable.to_json()) self.assertEqual(random.random, new_req._rand) self.assertEqual(time.sleep, new_req._sleep)
def test_auto_execute(): EnhancedBatchHttpRequest.CAP = 2 batcher = EnhancedBatchHttpRequest(None, batch_uri="http://localhost") with patch.object(EnhancedBatchHttpRequest, "execute") as mock: batcher.add(HttpRequest(None, None, None)) batcher.add(HttpRequest(None, None, None)) batcher.add(HttpRequest(None, None, None)) mock.assert_called_once()
def test_retry_connection_errors_non_resumable(self): model = JsonModel() request = HttpRequest( HttpMockWithErrors(3, {'status': '200'}, '{"foo": "bar"}'), model.response, u'https://www.example.com/json_api_endpoint') request._sleep = lambda _x: 0 # do nothing request._rand = lambda: 10 response = request.execute(num_retries=3) self.assertEqual({u'foo': u'bar'}, response)
def test_no_retry_connection_errors(self): model = JsonModel() request = HttpRequest( HttpMockWithNonRetriableErrors(1, {'status': '200'}, '{"foo": "bar"}'), model.response, u'https://www.example.com/json_api_endpoint') request._sleep = lambda _x: 0 # do nothing request._rand = lambda: 10 with self.assertRaises(socket.error): response = request.execute(num_retries=3)
def googe_doc_upload(filename, filetype, url): media = MediaFileUpload('files/' + filename, mimetype=filetype) file_stats = os.stat(filename) if file_stats.size <= 5e6: HttpRequest(url, method='POST', body=media).execute() else: # Resumable upload request = HttpRequest(url, method='POST', body=media, resumable=True) response = None while response is None: status, response = request.next_chunk()
def get(self, project, snapshot): if snapshot == valid_snapshot_name: http = HttpMock( 'tests/data/gcp/snapshots.get.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} ) elif snapshot == not_found_snapshot_name: http = HttpMock( 'tests/data/gcp/snapshots.get.notfound.json', {'status': '404'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} ) elif snapshot == invalid_snapshot_name: http = HttpMock( 'tests/data/gcp/snapshots.get.forbidden.json', {'status': '403'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} ) if snapshot == delete_snapshot_name: http = HttpMock( 'tests/data/gcp/snapshots.get.notfound.json', {'status': '404'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} )
def test_empty_content_type(self): """Test for #284""" http = HttpMock(None, headers={'status': 200}) uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar' method = u'POST' request = HttpRequest( http, _postproc_none, uri, method=method, headers={'content-type': ''}) request.execute() self.assertEqual('', http.headers.get('content-type'))
def log_and_send(self, desc: str, request: http.HttpRequest) -> Response: log.info(desc) log.debug(pprint.pformat(request)) response = request.execute() log.debug(pprint.pformat(response)) self.save_credentials() # They may have been refreshed in the process. return response
def test_unicode(self): http = HttpMock(datafile('zoo.json'), headers={'status': '200'}) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest(http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) request.execute() self.assertEqual(uri, http.uri) self.assertEqual(str, type(http.uri)) self.assertEqual(method, http.method) self.assertEqual(str, type(http.method))
def _execute_request( self, request: http.HttpRequest ) -> Mapping[Text, Sequence[Mapping[Text, Any]]]: result = request.execute() if 'error' in result: raise ValueError(result['error']) return result
def create(cluster_api: discovery.Resource, creds: Credentials, request: HttpRequest, project_id: str) -> "Optional[Cluster]": '''create cluster Note that this is a blocking call. Args: cluster_api: cluster api client cred: credentials request: cluster creation http request project_id: project id zone: zone Returns: Cluster instance on success, None otherwise ''' daemonset_url = utils.nvidia_daemonset_url(NodeImage.COS) body = json.loads(request.body) zone = body['cluster']['zone'] cluster_name = body['cluster']['name'] # execute rsp = request.execute() if rsp is None: logging.error('error: could not create cluster') return # wait for creation operation to complete operation_name = rsp['name'] rsp = utils.wait_for_operation( cluster_api, 'projects/{}/locations/{}/operations/{}'.format( project_id, zone, operation_name)) if rsp['status'] != OpStatus.DONE.value: logging.error('error creating cluster {}!'.format(cluster_name)) return # get our newly-created cluster cluster = Cluster.get(name=cluster_name, project_id=project_id, zone=zone, creds=creds) if cluster is None: logging.error( 'error: unable to connect to cluster {}'.format(cluster_name)) logging.error( 'nvidia-driver daemonset not applied, to do this manually:') logging.error('kubectl apply -f {}'.format(daemonset_url)) return logging.info('created cluster {} successfully'.format(cluster_name)) logging.info('applying nvidia driver daemonset...') rsp = cluster.apply_daemonset_from_url( daemonset_url, lambda x: yaml.load(x, Loader=yaml.FullLoader)) return cluster
def test_ensure_response_callback(self): m = JsonModel() request = HttpRequest( None, m.response, 'https://www.googleapis.com/someapi/v1/collection/?foo=bar', method='POST', body='{}', headers={'content-type': 'application/json'}) h = HttpMockSequence([ ({'status': 200}, '{}')]) responses = [] def _on_response(resp, responses=responses): responses.append(resp) request.add_response_callback(_on_response) request.execute(http=h) self.assertEqual(1, len(responses))
def test_unicode(self): http = HttpMock(datafile('zoo.json'), headers={'status': '200'}) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest( http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) request.execute() self.assertEqual(uri, http.uri) self.assertEqual(str, type(http.uri)) self.assertEqual(method, http.method) self.assertEqual(str, type(http.method))
def setUp(self): model = JsonModel() self.request1 = HttpRequest( None, model.response, 'https://www.googleapis.com/someapi/v1/collection/?foo=bar', method='POST', body='{}', headers={'content-type': 'application/json'}) self.request2 = HttpRequest( None, model.response, 'https://www.googleapis.com/someapi/v1/collection/?foo=bar', method='GET', body='', headers={'content-type': 'application/json'})
def test_retry_connection_errors_resumable(self): with open(datafile('small.png'), 'rb') as small_png_file: small_png_fd = BytesIO(small_png_file.read()) upload = MediaIoBaseUpload(fd=small_png_fd, mimetype='image/png', chunksize=500, resumable=True) model = JsonModel() request = HttpRequest( HttpMockWithErrors( 3, {'status': '200', 'location': 'location'}, '{"foo": "bar"}'), model.response, u'https://www.example.com/file_upload', method='POST', resumable=upload) request._sleep = lambda _x: 0 # do nothing request._rand = lambda: 10 response = request.execute(num_retries=3) self.assertEqual({u'foo': u'bar'}, response)
def test_error_response(self): http = HttpMock(datafile('bad_request.json'), {'status': '400'}) model = JsonModel() request = HttpRequest( http, model.response, 'https://www.googleapis.com/someapi/v1/collection/?foo=bar', method='GET', headers={}) self.assertRaises(HttpError, request.execute)
def get(self, project, zone, operation): if operation == valid_operation_id: http = HttpMock( 'tests/data/gcp/zoneOperations.get.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} ) elif operation == pending_operation_id: http = HttpMock( 'tests/data/gcp/zoneOperations.get.running.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} ) elif operation == error_operation_id: http = HttpMock( 'tests/data/gcp/zoneOperations.get.error.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest( http, model.response, uri, method=method, headers={} ) else: return None
def aggregatedList(self, project, filter=None): http = HttpMock('tests/data/gcp/instances.aggregatedList.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest(http, model.response, uri, method=method, headers={})
def test_media_io_base_next_chunk_retries(self): try: import io except ImportError: return f = open(datafile('small.png'), 'r') fd = io.BytesIO(f.read()) upload = MediaIoBaseUpload( fd=fd, mimetype='image/png', chunksize=500, resumable=True) # Simulate 5XXs for both the request that creates the resumable upload and # the upload itself. http = HttpMockSequence([ ({'status': '500'}, ''), ({'status': '500'}, ''), ({'status': '503'}, ''), ({'status': '200', 'location': 'location'}, ''), ({'status': '500'}, ''), ({'status': '500'}, ''), ({'status': '503'}, ''), ({'status': '200'}, '{}'), ]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar' method = u'POST' request = HttpRequest( http, model.response, uri, method=method, headers={}, resumable=upload) sleeptimes = [] request._sleep = lambda x: sleeptimes.append(x) request._rand = lambda: 10 request.execute(num_retries=3) self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
def delete(self, project, zone, disk): if disk == delete_disk_name or disk == valid_disk_name: http = HttpMock('tests/data/gcp/disks.delete.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'DELETE' return HttpRequest(http, model.response, uri, method=method, headers={})
def delete(self, project, snapshot): if snapshot == delete_snapshot_name or snapshot == valid_snapshot_name: http = HttpMock('tests/data/gcp/snapshots.delete.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'DELETE' return HttpRequest(http, model.response, uri, method=method, headers={})
def detachDisk(self, project, zone, instance, deviceName): if instance == valid_vm_id: http = HttpMock('tests/data/gcp/instances.detachDisk.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'POST' return HttpRequest(http, model.response, uri, method=method, headers={})
def test_retry(self): num_retries = 5 resp_seq = [({'status': '500'}, '')] * (num_retries - 3) resp_seq.append(({'status': '403'}, RATE_LIMIT_EXCEEDED_RESPONSE)) resp_seq.append(({'status': '403'}, USER_RATE_LIMIT_EXCEEDED_RESPONSE)) resp_seq.append(({'status': '429'}, '')) resp_seq.append(({'status': '200'}, '{}')) http = HttpMockSequence(resp_seq) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest(http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) sleeptimes = [] request._sleep = lambda x: sleeptimes.append(x) request._rand = lambda: 10 request.execute(num_retries=num_retries) self.assertEqual(num_retries, len(sleeptimes)) for retry_num in range(num_retries): self.assertEqual(10 * 2**(retry_num + 1), sleeptimes[retry_num])
def test_no_retry_fails_fast(self): http = HttpMockSequence([({ 'status': '500' }, ''), ({ 'status': '200' }, '{}')]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest(http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) request._rand = lambda: 1.0 request._sleep = lambda _: self.fail( 'sleep should not have been called.') try: request.execute() self.fail('Should have raised an exception.') except HttpError: pass
def test_media_io_base_next_chunk_no_retry_403_not_configured(self): fd = BytesIO(b"i am png") upload = MediaIoBaseUpload(fd=fd, mimetype='image/png', chunksize=500, resumable=True) http = HttpMockSequence([({ 'status': '403' }, NOT_CONFIGURED_RESPONSE), ({ 'status': '200' }, '{}')]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar' method = u'POST' request = HttpRequest(http, model.response, uri, method=method, headers={}, resumable=upload) request._rand = lambda: 1.0 request._sleep = mock.MagicMock() with self.assertRaises(HttpError): request.execute(num_retries=3) request._sleep.assert_not_called()
def test_serialize_get_request_no_body(self): batch = BatchHttpRequest() request = HttpRequest( None, None, 'https://www.googleapis.com/someapi/v1/collection/?foo=bar', method='GET', body=None, headers={'content-type': 'application/json'}, methodId=None, resumable=None) s = batch._serialize_request(request).splitlines() self.assertEqual(NO_BODY_EXPECTED_GET.splitlines(), s)
def test_no_retry_401_fails_fast(self): http = HttpMockSequence([ ({'status': '401'}, ''), ({'status': '200'}, '{}') ]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest( http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) request._rand = lambda: 1.0 request._sleep = mock.MagicMock() with self.assertRaises(HttpError): request.execute() request._sleep.assert_not_called()
def test_media_io_base_next_chunk_retries(self): try: import io except ImportError: return f = open(datafile('small.png'), 'r') fd = io.BytesIO(f.read()) upload = MediaIoBaseUpload(fd=fd, mimetype='image/png', chunksize=500, resumable=True) # Simulate 5XXs for both the request that creates the resumable upload and # the upload itself. http = HttpMockSequence([ ({ 'status': '500' }, ''), ({ 'status': '500' }, ''), ({ 'status': '503' }, ''), ({ 'status': '200', 'location': 'location' }, ''), ({ 'status': '500' }, ''), ({ 'status': '500' }, ''), ({ 'status': '503' }, ''), ({ 'status': '200' }, '{}'), ]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/upload/?foo=bar' method = u'POST' request = HttpRequest(http, model.response, uri, method=method, headers={}, resumable=upload) sleeptimes = [] request._sleep = lambda x: sleeptimes.append(x) request._rand = lambda: 10 request.execute(num_retries=3) self.assertEqual([20, 40, 80, 20, 40, 80], sleeptimes)
def test_add_fail_for_over_limit(self): from googleapiclient.http import MAX_BATCH_LIMIT batch = BatchHttpRequest() for i in range(0, MAX_BATCH_LIMIT): batch.add(HttpRequest( None, None, 'https://www.googleapis.com/someapi/v1/collection/?foo=bar', method='POST', body='{}', headers={'content-type': 'application/json'}) ) self.assertRaises(BatchError, batch.add, self.request1)
def get(self, project, zone, instance): if instance == valid_vm_id: http = HttpMock('tests/data/gcp/instances.get.json', {'status': '200'}) model = JsonModel() uri = 'some_uri' method = 'GET' return HttpRequest(http, model.response, uri, method=method, headers={}) else: return None
def test_no_retry_fails_fast(self): http = HttpMockSequence([ ({'status': '500'}, ''), ({'status': '200'}, '{}') ]) model = JsonModel() uri = u'https://www.googleapis.com/someapi/v1/collection/?foo=bar' method = u'POST' request = HttpRequest( http, model.response, uri, method=method, body=u'{}', headers={'content-type': 'application/json'}) request._rand = lambda: 1.0 request._sleep = lambda _: self.fail('sleep should not have been called.') try: request.execute() self.fail('Should have raised an exception.') except HttpError: pass
def test_turn_get_into_post(self): def _postproc(resp, content): return content http = HttpMockSequence([ ({'status': '200'}, 'echo_request_body'), ({'status': '200'}, 'echo_request_headers'), ]) # Send a long query parameter. query = { 'q': 'a' * MAX_URI_LENGTH + '?&' } req = HttpRequest( http, _postproc, 'http://example.com?' + urllib.urlencode(query), method='GET', body=None, headers={}, methodId='foo', resumable=None) # Query parameters should be sent in the body. response = req.execute() self.assertEqual('q=' + 'a' * MAX_URI_LENGTH + '%3F%26', response) # Extra headers should be set. response = req.execute() self.assertEqual('GET', response['x-http-method-override']) self.assertEqual(str(MAX_URI_LENGTH + 8), response['content-length']) self.assertEqual( 'application/x-www-form-urlencoded', response['content-type'])