def post(self): """Handle POST requests by executing the API call.""" if not self.CheckIsAdmin(): return self.response.headers['Content-Type'] = 'text/plain' response = remote_api_pb.Response() try: request = remote_api_pb.Request() request.ParseFromString(self.request.body) response_data = self.ExecuteRequest(request) response.set_response(response_data.Encode()) self.response.set_status(200) except Exception as e: logging.exception('Exception while handling %s', request) self.response.set_status(200) response.set_exception(pickle.dumps(e)) if isinstance(e, apiproxy_errors.ApplicationError): application_error = response.mutable_application_error() application_error.set_code(e.application_error) application_error.set_detail(e.error_detail) self.response.out.write(response.Encode())
def MakeSyncCall(self, service, call, request, response, request_id=None): """ The main RPC entry point. Args: service: Must be name as provided to service_name of constructor. call: A string representing the rpc to make. Must be part of the underlying services methods and impemented by _Dynamic_<call>. request: A protocol buffer of the type corresponding to 'call'. response: A protocol buffer of the type corresponding to 'call'. request_id: A unique string identifying the request associated with the API call. """ assert service == self._service_name, ('Expected "%s" service name, ' 'was "%s"' % (self._service_name, service)) if request.ByteSize() > self._max_request_size: raise apiproxy_errors.RequestTooLargeError( 'The request to API call %s.%s() was too large.' % (service, call)) messages = [] assert request.IsInitialized(messages), messages remote_api_request = remote_api_pb.Request() remote_api_request.set_service_name(service) remote_api_request.set_method(call) remote_api_request.set_request(request.Encode()) if request_id is not None: remote_api_request.set_request_id(request_id) url = 'http://{}'.format(self._location) request_handle = urllib2.Request(url, remote_api_request.Encode()) response_handle = urllib2.urlopen(request_handle) remote_api_response = remote_api_pb.Response(response_handle.read()) if remote_api_response.has_application_error(): error_pb = remote_api_response.application_error() raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) if remote_api_response.has_exception(): raise pickle.loads(remote_api_response.exception()) response.ParseFromString(remote_api_response.response())
def _RemoteSend(self, request, response, method, request_id=None): """Sends a request remotely to the taskqueue server. Args: request: A protocol buffer request. response: A protocol buffer response. method: The function which is calling the remote server. request_id: A string specifying a request ID. Raises: taskqueue_service_pb.InternalError: """ tag = self.__app_id api_request = remote_api_pb.Request() api_request.set_method(method) api_request.set_service_name("taskqueue") api_request.set_request(request.Encode()) if request_id is not None: api_request.set_request_id(request_id) tq_locations = self._GetTQLocations() for index, tq_location in enumerate(tq_locations): api_response = remote_api_pb.Response() api_response = api_request.sendCommand(tq_location, tag, api_response, 1, False, KEY_LOCATION, CERT_LOCATION) if not api_response or not api_response.has_response(): if index >= len(tq_locations) - 1: raise apiproxy_errors.ApplicationError( taskqueue_service_pb.TaskQueueServiceError. INTERNAL_ERROR) else: break if api_response.has_application_error(): error_pb = api_response.application_error() logging.error(error_pb.detail()) raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) if api_response.has_exception(): raise api_response.exception() response.ParseFromString(api_response.response())
def _RemoteSend(self, request, response, method, request_id=None): """Sends a request remotely to the datstore server. """ tag = self.__app_id self._maybeSetDefaultAuthDomain() user = users.GetCurrentUser() if user != None: tag += ":" + user.email() tag += ":" + user.nickname() tag += ":" + user.auth_domain() api_request = remote_api_pb.Request() api_request.set_method(method) api_request.set_service_name("datastore_v3") api_request.set_request(request.Encode()) if request_id is not None: api_request.set_request_id(request_id) api_response = remote_api_pb.Response() try: api_response = api_request.sendCommand(self.__datastore_location, tag, api_response, 1, self.__is_encrypted, KEY_LOCATION, CERT_LOCATION) except socket.error as socket_error: if socket_error.errno == errno.ETIMEDOUT: raise apiproxy_errors.ApplicationError( datastore_pb.Error.TIMEOUT, 'Connection timed out when making datastore request') raise if not api_response or not api_response.has_response(): raise datastore_errors.InternalError( 'No response from db server on %s requests.' % method) if api_response.has_application_error(): error_pb = api_response.application_error() logging.error(error_pb.detail()) raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) if api_response.has_exception(): raise api_response.exception() response.ParseFromString(api_response.response())
def _MakeRemoteSyncCall(self, service, call, request, response): """Send an RPC to a remote_api endpoint.""" request_pb = remote_api_pb.Request() request_pb.set_service_name(service) request_pb.set_method(call) request_pb.set_request(request.Encode()) response_pb = remote_api_pb.Response() encoded_request = request_pb.Encode() try: urlfetch_response = urlfetch.fetch(self.remote_url, encoded_request, urlfetch.POST, self.extra_headers, follow_redirects=False, deadline=10) except Exception, e: logging.exception('Fetch failed to %s', self.remote_url) raise FetchFailed(e)
def CreateRequestPB(package, call, ticket, body): """Create a remote_api_pb.Request functionally. Args: package: String to set as the service name (package name). call: String to set as the method (API call name). ticket: String to set as the request ID (ticket). body: String to set as the request body. Returns: A remote_api_pb.Request. """ request = remote_api_pb.Request() request.set_service_name(package) request.set_method(call) request.set_request_id(ticket) request.set_request(body) return request
def _handle_POST(self, environ, start_response): start_response('200 OK', [('Content-Type', 'application/octet-stream')]) start_time = time.time() response = remote_api_pb.Response() try: request = remote_api_pb.Request() # NOTE: Exceptions encountered when parsing the PB or handling the request # will be propagated back to the caller the same way as exceptions raised # by the actual API call. if environ.get('HTTP_TRANSFER_ENCODING') == 'chunked': # CherryPy concatenates all chunks when 'wsgi.input' is read but v3.2.2 # will not return even when all of the data in all chunks has been # read. See: https://bitbucket.org/cherrypy/cherrypy/issue/1131. wsgi_input = environ['wsgi.input'].read(2**32) else: wsgi_input = environ['wsgi.input'].read( int(environ['CONTENT_LENGTH'])) request.ParseFromString(wsgi_input) with self._request_context(environ) as request_id: api_response = _execute_request(request, request_id).Encode() response.set_response(api_response) except Exception, e: if isinstance(e, apiproxy_errors.ApplicationError): level = logging.DEBUG application_error = response.mutable_application_error() application_error.set_code(e.application_error) application_error.set_detail(e.error_detail) # TODO: is this necessary? Python remote stub ignores exception # when application error is specified; do other runtimes use it? response.set_exception(pickle.dumps(e)) else: # If the runtime instance is not Python, it won't be able to unpickle # the exception so use level that won't be ignored by default. level = logging.ERROR # Even if the runtime is Python, the exception may be unpicklable if # it requires importing a class blocked by the sandbox so just send # back the exception representation. response.set_exception(pickle.dumps(RuntimeError(repr(e)))) logging.log(level, 'Exception while handling %s\n%s', request, traceback.format_exc())
def _RemoteSend(self, request, response, method): """ Sends a request remotely to the search server. Args: request: A request object. response: A response object to be filled in. method: A str, the dynamic function doing the call. """ if not self.__search_location: raise search.InternalError("Search service not configured.") api_request = remote_api_pb.Request() api_request.set_method(method) api_request.set_service_name("search") api_request.set_request(request.Encode()) api_response = remote_api_pb.Response() api_response = api_request.sendCommand(self.__search_location, "", api_response, 1, False, KEY_LOCATION, CERT_LOCATION) if api_response.has_application_error(): error_pb = api_response.application_error() logging.error(error_pb.detail()) raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) if api_response.has_exception(): raise api_response.exception() if not api_response or not api_response.has_response(): raise search.InternalError( 'No response from search server on %s requests.' % method) response.ParseFromString(api_response.response())
def _MakeRealSyncCall(self, service, call, request, response): request_pb = remote_api_pb.Request() request_pb.set_service_name(service) request_pb.set_method(call) request_pb.set_request(request.Encode()) response_pb = remote_api_pb.Response() encoded_request = request_pb.Encode() encoded_response = self._server.Send(self._path, encoded_request) response_pb.ParseFromString(encoded_response) if response_pb.has_application_error(): error_pb = response_pb.application_error() raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) elif response_pb.has_exception(): raise pickle.loads(response_pb.exception()) elif response_pb.has_java_exception(): raise UnknownJavaServerError("An unknown error has occured in the " "Java remote_api handler for this call.") else: response.ParseFromString(response_pb.response())
def _MakeRemoteSyncCall(self, service, call, request, response): """Send an RPC to a remote_api endpoint.""" request_pb = remote_api_pb.Request() request_pb.set_service_name(service) request_pb.set_method(call) request_pb.set_request(request.Encode()) response_pb = remote_api_pb.Response() encoded_request = request_pb.Encode() try: urlfetch_response = urlfetch.fetch(self.remote_url, encoded_request, urlfetch.POST, self.extra_headers, follow_redirects=False, deadline=10) except Exception as e: logging.exception('Fetch failed to %s', self.remote_url) raise FetchFailed(e) if urlfetch_response.status_code != 200: logging.error('Fetch failed to %s; Status %s; body %s', self.remote_url, urlfetch_response.status_code, urlfetch_response.content) raise FetchFailed(urlfetch_response.status_code) response_pb.ParseFromString(urlfetch_response.content) if response_pb.has_application_error(): error_pb = response_pb.application_error() raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) elif response_pb.has_exception(): raise pickle.loads(response_pb.exception()) elif response_pb.has_java_exception(): raise UnknownJavaServerError( 'An unknown error has occured in the ' 'Java remote_api handler for this call.') else: response.ParseFromString(response_pb.response())
def _make_request(self, method, body): request = remote_api_pb.Request() request.set_service_name(self.SERVICE_NAME) request.set_method(method) request.set_request(body) location = random.choice(self._locations) url = 'http://{}'.format(location) headers = {'protocolbuffertype': 'Request', 'appdata': PROJECT_ID} response = yield self._client.fetch(url, method='POST', body=request.Encode(), headers=headers) api_response = remote_api_pb.Response(response.body) if api_response.has_application_error(): raise DatastoreError(api_response.application_error().detail()) if api_response.has_exception(): raise DatastoreError(str(api_response.exception())) raise gen.Return(api_response.response())
def do_POST(self): """Handles a single API request e.g. memcache.Get().""" self.send_response(httplib.OK) self.send_header('Content-Type', 'application/octet-stream') self.end_headers() response = remote_api_pb.Response() try: request = remote_api_pb.Request() request.ParseFromString( self.rfile.read(int(self.headers['content-length']))) api_response = _ExecuteRequest(request).Encode() response.set_response(api_response) except Exception, e: logging.debug('Exception while handling %s\n%s', request, traceback.format_exc()) response.set_exception(pickle.dumps(e)) if isinstance(e, apiproxy_errors.ApplicationError): application_error = response.mutable_application_error() application_error.set_code(e.application_error) application_error.set_detail(e.error_detail)
def handle_read(self): self.data += self.recv(8192) if self.n == -1: i = self.data.find('\n') if i == -1: return try: self.n = int(self.data[:i]) except: self.n = -2 if self.n < 0: self.n = -2 self.data = '' return self.data = self.data[i+1:] elif self.n == -2: self.data = '' return if len(self.data) < self.n: return req = remote_api_pb.Request() req.ParseFromString(self.data[:self.n]) self.data, self.n = self.data[self.n:], -1 rapi_result = None rapi_error = 'unknown error' try: rapi_result = RAPI_HANDLER.ExecuteRequest(req) except apiproxy_errors.CallNotFoundError, e: service_name = req.service_name() method = req.method() rapi_error = 'call not found for %s/%s' % (service_name, method)
def _MakeCallImpl(self): """Makes an asynchronous API call over the service bridge. For this to work the following must be set: self.package: the API package name; self.call: the name of the API call/method to invoke; self.request: the API request body as a serialized protocol buffer. The actual API call is made by requests.post via a thread pool (multiprocessing.dummy.Pool). The thread pool restricts the number of concurrent requests to MAX_CONCURRENT_API_CALLS, so this method will block if that limit is exceeded, until other asynchronous calls resolve. If the main thread holds the import lock, waiting on thread work can cause a deadlock: https://docs.python.org/2/library/threading.html#importing-in-threaded-code Therefore, we try to detect this error case and fall back to sync calls. """ assert self._state == apiproxy_rpc.RPC.IDLE, self._state self.lock = threading.Lock() self.event = threading.Event() if VMStub.ShouldUseRequestSecurityTicketForThread(): ticket = os.environ.get( TICKET_HEADER, os.environ.get(DEV_TICKET_HEADER, self.stub.DefaultTicket())) else: ticket = self.stub.DefaultTicket() request = remote_api_pb.Request() request.set_service_name(self.package) request.set_method(self.call) request.set_request_id(ticket) request.set_request(self.request.SerializeToString()) deadline = self.deadline or DEFAULT_TIMEOUT body_data = request.SerializeToString() headers = { SERVICE_DEADLINE_HEADER: str(deadline), SERVICE_ENDPOINT_HEADER: SERVICE_ENDPOINT_NAME, SERVICE_METHOD_HEADER: APIHOST_METHOD, 'Content-type': RPC_CONTENT_TYPE, } dapper_header_value = os.environ.get(DAPPER_ENV_KEY) if dapper_header_value: headers[DAPPER_HEADER] = dapper_header_value api_host = os.environ.get('API_HOST', SERVICE_BRIDGE_HOST) api_port = os.environ.get('API_PORT', API_PORT) endpoint_url = urlparse.urlunparse( ('http', '%s:%s' % (api_host, api_port), PROXY_PATH, '', '', '')) self._state = apiproxy_rpc.RPC.RUNNING request_kwargs = dict(url=endpoint_url, timeout=DEADLINE_DELTA_SECONDS + deadline, headers=headers, data=body_data) if imp.lock_held() and not app_is_loaded: try: value = CaptureStacktrace(requests.post, **request_kwargs) success = True except Exception as e: value = e success = False self._result_future = SyncResult(value, success) else: self._result_future = self.stub.thread_pool.apply_async( CaptureStacktrace, args=[requests.post], kwds=request_kwargs)
def remote_request(self, app_info, http_request_data): """ Receives a remote request to which it should give the correct response. The http_request_data holds an encoded protocol buffer of a certain type. Each type has a particular response type. Args: app_info: A dictionary containing the application, module, and version ID of the app that is sending this request. http_request_data: Encoded protocol buffer. """ apirequest = remote_api_pb.Request() apirequest.ParseFromString(http_request_data) apiresponse = remote_api_pb.Response() response = None errcode = 0 errdetail = "" method = "" http_request_data = "" app_id = app_info['app_id'] if not apirequest.has_method(): errcode = taskqueue_service_pb.TaskQueueServiceError.INVALID_REQUEST errdetail = "Method was not set in request" apirequest.set_method("NOT_FOUND") else: method = apirequest.method() if not apirequest.has_request(): errcode = taskqueue_service_pb.TaskQueueServiceError.INVALID_REQUEST errdetail = "Request missing in call" apirequest.set_method("NOT_FOUND") apirequest.clear_request() else: http_request_data = apirequest.request() start_time = time.time() request_log = method if apirequest.has_request_id(): request_log += ': {}'.format(apirequest.request_id()) logger.debug(request_log) result = None if method == "FetchQueueStats": result = self.queue_handler.fetch_queue_stats( app_id, http_request_data) elif method == "PurgeQueue": result = self.queue_handler.purge_queue(app_id, http_request_data) elif method == "Delete": result = self.queue_handler.delete(app_id, http_request_data) elif method == "QueryAndOwnTasks": result = self.queue_handler.query_and_own_tasks( app_id, http_request_data) elif method == "Add": result = self.queue_handler.add(app_info, http_request_data) elif method == "BulkAdd": result = self.queue_handler.bulk_add(app_info, http_request_data) elif method == "ModifyTaskLease": result = self.queue_handler.modify_task_lease( app_id, http_request_data) elif method == "UpdateQueue": response = taskqueue_service_pb.TaskQueueUpdateQueueResponse() result = self.queue_handler.Encode(), 0, "" elif method == "FetchQueues": result = self.queue_handler.fetch_queue(app_id, http_request_data) elif method == "QueryTasks": result = self.queue_handler.query_tasks(app_id, http_request_data) elif method == "FetchTask": result = self.queue_handler.fetch_task(app_id, http_request_data) elif method == "ForceRun": result = self.queue_handler.force_run(app_id, http_request_data) elif method == "DeleteQueue": response = taskqueue_service_pb.TaskQueueDeleteQueueResponse() result = self.queue_handler.Encode(), 0, "" elif method == "PauseQueue": result = self.queue_handler.pause_queue(app_id, http_request_data) elif method == "DeleteGroup": result = self.queue_handler.delete_group(app_id, http_request_data) elif method == "UpdateStorageLimit": result = self.queue_handler.update_storage_limit( app_id, http_request_data) if result: response, errcode, errdetail = result elapsed_time = round(time.time() - start_time, 3) timing_log = 'Elapsed: {}'.format(elapsed_time) if apirequest.has_request_id(): timing_log += ' ({})'.format(apirequest.request_id()) logger.debug(timing_log) if response is not None: apiresponse.set_response(response) # If there was an error add it to the response. if errcode != 0: apperror_pb = apiresponse.mutable_application_error() apperror_pb.set_code(errcode) apperror_pb.set_detail(errdetail) self.write(apiresponse.Encode()) status = taskqueue_service_pb.TaskQueueServiceError.ErrorCode_Name( errcode) return method, status
def _RemoteSend(self, request, response, method, request_id=None, service_id=None, version_id=None): """Sends a request remotely to the taskqueue server. Args: request: A protocol buffer request. response: A protocol buffer response. method: The function which is calling the remote server. request_id: A string specifying a request ID. service_id: A string specifying the client service ID. version_id: A string specifying the client version ID. Raises: taskqueue_service_pb.InternalError: """ tag = self.__app_id api_request = remote_api_pb.Request() api_request.set_method(method) api_request.set_service_name("taskqueue") api_request.set_request(request.Encode()) if request_id is not None: api_request.set_request_id(request_id) api_response = remote_api_pb.Response() retry_count = 0 max_retries = 3 location = random.choice(self.__tq_locations) while True: try: api_request.sendCommand(location, tag, api_response, 1, False, KEY_LOCATION, CERT_LOCATION) break except socket.error as socket_error: if socket_error.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH): backoff_ms = 500 * 3**retry_count # 0.5s, 1.5s, 4.5s retry_count += 1 if retry_count > max_retries: raise logging.warning( 'Failed to call {} method of TaskQueue ({}). Retry #{} in {}ms.' .format(method, socket_error, retry_count, backoff_ms)) time.sleep(float(backoff_ms) / 1000) location = random.choice(self.__tq_locations) api_response = remote_api_pb.Response() continue if socket_error.errno == errno.ETIMEDOUT: raise apiproxy_errors.ApplicationError( taskqueue_service_pb.TaskQueueServiceError. INTERNAL_ERROR, 'Connection timed out when making taskqueue request') raise # AppScale: Interpret ProtocolBuffer.ProtocolBufferReturnError as # datastore_errors.InternalError except ProtocolBuffer.ProtocolBufferReturnError as e: raise apiproxy_errors.ApplicationError( taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR, str(e)) if not api_response or not api_response.has_response(): raise apiproxy_errors.ApplicationError( taskqueue_service_pb.TaskQueueServiceError.INTERNAL_ERROR) if api_response.has_application_error(): error_pb = api_response.application_error() logging.error(error_pb.detail()) raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) if api_response.has_exception(): raise api_response.exception() response.ParseFromString(api_response.response())
def remote_request(self, app_id, http_request_data, service_id, version_id): """ Receives a remote request to which it should give the correct response. The http_request_data holds an encoded protocol buffer of a certain type. Each type has a particular response type. Args: app_id: The application ID that is sending this request. http_request_data: Encoded protocol buffer. service_id: A string specifying the client's service ID. version_id: A string specifying the client's version ID. """ apirequest = remote_api_pb.Request() apirequest.ParseFromString(http_request_data) apiresponse = remote_api_pb.Response() response = None if not apirequest.has_method(): apirequest.set_method("NOT_FOUND") if not apirequest.has_request(): apirequest.set_method("NOT_FOUND") apirequest.clear_request() method = apirequest.method() http_request_data = apirequest.request() start = time.time() request_log = method if apirequest.has_request_id(): request_log += ': {}'.format(apirequest.request_id()) logger.debug(request_log) if method == "Put": response, errcode, errdetail = yield self.put_request( app_id, http_request_data) elif method == "Get": response, errcode, errdetail = yield self.get_request( app_id, http_request_data) elif method == "Delete": response, errcode, errdetail = yield self.delete_request( app_id, http_request_data) elif method == "RunQuery": response, errcode, errdetail = yield self.run_query(http_request_data) elif method == "BeginTransaction": response, errcode, errdetail = yield self.begin_transaction_request( app_id, http_request_data) elif method == "Commit": response, errcode, errdetail = yield self.commit_transaction_request( app_id, http_request_data) elif method == "Rollback": response, errcode, errdetail = self.rollback_transaction_request( app_id, http_request_data) elif method == "AllocateIds": response, errcode, errdetail = yield self.allocate_ids_request( app_id, http_request_data) elif method == "CreateIndex": response, errcode, errdetail = yield self.create_index_request( app_id, http_request_data) elif method == "GetIndices": response, errcode, errdetail = yield self.get_indices_request(app_id) elif method == "UpdateIndex": response, errcode, errdetail = self.update_index_request( app_id, http_request_data) elif method == "DeleteIndex": response, errcode, errdetail = yield self.delete_index_request( app_id, http_request_data) elif method == 'AddActions': response, errcode, errdetail = yield self.add_actions_request( app_id, http_request_data, service_id, version_id) elif method == 'datastore_v4.AllocateIds': response, errcode, errdetail = yield self.v4_allocate_ids_request( app_id, http_request_data) else: errcode = datastore_pb.Error.BAD_REQUEST errdetail = "Unknown datastore message" time_taken = time.time() - start if method in STATS: if errcode in STATS[method]: prev_req, pre_time = STATS[method][errcode] STATS[method][errcode] = prev_req + 1, pre_time + time_taken else: STATS[method][errcode] = (1, time_taken) else: STATS[method] = {} STATS[method][errcode] = (1, time_taken) apiresponse.set_response(response) if errcode != 0: apperror_pb = apiresponse.mutable_application_error() apperror_pb.set_code(errcode) apperror_pb.set_detail(errdetail) self.write(apiresponse.Encode())
def _MakeCallImpl(self): """Makes an asynchronous API call over the service bridge. For this to work the following must be set: self.package: the API package name; self.call: the name of the API call/method to invoke; self.request: the API request body as a serialized protocol buffer. The actual API call is made by requests.post via a thread pool (multiprocessing.dummy.Pool). The thread pool restricts the number of concurrent requests to MAX_CONCURRENT_API_CALLS, so this method will block if that limit is exceeded, until other asynchronous calls resolve. """ assert self._state == apiproxy_rpc.RPC.IDLE, self._state self.lock = threading.Lock() self.event = threading.Event() if VMStub.ShouldUseRequestSecurityTicketForThread(): ticket = os.environ.get( TICKET_HEADER, os.environ.get(DEV_TICKET_HEADER, self.stub.DefaultTicket())) else: ticket = self.stub.DefaultTicket() request = remote_api_pb.Request() request.set_service_name(self.package.encode()) request.set_method(self.call.encode()) request.set_request_id(ticket.encode()) request.set_request(self.request.SerializeToString()) deadline = self.deadline or DEFAULT_TIMEOUT body_data = request.SerializeToString() headers = { SERVICE_DEADLINE_HEADER: deadline, SERVICE_ENDPOINT_HEADER: SERVICE_ENDPOINT_NAME, SERVICE_METHOD_HEADER: APIHOST_METHOD, 'Content-type': RPC_CONTENT_TYPE, } dapper_header_value = os.environ.get(DAPPER_ENV_KEY) if dapper_header_value: headers[DAPPER_HEADER] = dapper_header_value api_host = os.environ.get('API_HOST', SERVICE_BRIDGE_HOST) api_port = os.environ.get('API_PORT', API_PORT) endpoint_url = urllib.parse.urlunparse( ('http', '%s:%s' % (api_host, api_port), PROXY_PATH, '', '', '')) self._state = apiproxy_rpc.RPC.RUNNING request_kwargs = dict(url=endpoint_url, timeout=DEADLINE_DELTA_SECONDS + deadline, headers=headers, data=body_data) self._result_future = self.stub.thread_pool.apply_async( requests.post, kwds=request_kwargs)
def remote_request(self, app_id, http_request_data): """ Receives a remote request to which it should give the correct response. The http_request_data holds an encoded protocol buffer of a certain type. Each type has a particular response type. Args: app_id: The application ID that is sending this request. http_request_data: Encoded protocol buffer. """ global task_queue apirequest = remote_api_pb.Request() apirequest.ParseFromString(http_request_data) apiresponse = remote_api_pb.Response() response = None errcode = 0 errdetail = "" apperror_pb = None method = "" http_request_data = "" if not apirequest.has_method(): errcode = taskqueue_service_pb.TaskQueueServiceError.INVALID_REQUEST errdetail = "Method was not set in request" apirequest.set_method("NOT_FOUND") else: method = apirequest.method() if not apirequest.has_request(): errcode = taskqueue_service_pb.TaskQueueServiceError.INVALID_REQUEST errdetail = "Request missing in call" apirequest.set_method("NOT_FOUND") apirequest.clear_request() else: http_request_data = apirequest.request() if method == "FetchQueueStats": response, errcode, errdetail = task_queue.fetch_queue_stats( app_id, http_request_data) elif method == "PurgeQueue": response, errcode, errdetail = task_queue.purge_queue( app_id, http_request_data) elif method == "Delete": response, errcode, errdetail = task_queue.delete( app_id, http_request_data) elif method == "QueryAndOwnTasks": response, errcode, errdetail = task_queue.query_and_own_tasks( app_id, http_request_data) elif method == "Add": response, errcode, errdetail = task_queue.add( app_id, http_request_data) elif method == "BulkAdd": response, errcode, errdetail = task_queue.bulk_add( app_id, http_request_data) elif method == "ModifyTaskLease": response, errcode, errdetail = task_queue.modify_task_lease( app_id, http_request_data) elif method == "UpdateQueue": response, errcode, errdetail = task_queue.update_queue( app_id, http_request_data) elif method == "FetchQueues": response, errcode, errdetail = task_queue.fetch_queue( app_id, http_request_data) elif method == "QueryTasks": response, errcode, errdetail = task_queue.query_tasks( app_id, http_request_data) elif method == "FetchTask": response, errcode, errdetail = task_queue.fetch_task( app_id, http_request_data) elif method == "ForceRun": response, errcode, errdetail = task_queue.force_run( app_id, http_request_data) elif method == "DeleteQueue": response, errcode, errdetail = task_queue.delete_queue( app_id, http_request_data) elif method == "PauseQueue": response, errcode, errdetail = task_queue.pause_queue( app_id, http_request_data) elif method == "DeleteGroup": response, errcode, errdetail = task_queue.delete_group( app_id, http_request_data) elif method == "UpdateStorageLimit": response, errcode, errdetail = task_queue.update_storage_limit( app_id, http_request_data) if response: apiresponse.set_response(response) # If there was an error add it to the response. if errcode != 0: apperror_pb = apiresponse.mutable_application_error() apperror_pb.set_code(errcode) apperror_pb.set_detail(errdetail) self.write(apiresponse.Encode())
def _handle_POST(self, environ, start_response): """Handles a POST request containing a serialized remote_api_pb.Request. Args: environ: An environ dict for the request as defined in PEP-333. start_response: A start_response function with semantics defined in PEP-333. Returns: A single element list containing the string body of the HTTP response. """ start_response('200 OK', [('Content-Type', 'application/octet-stream')]) start_time = time.time() response = remote_api_pb.Response() try: request = remote_api_pb.Request() # NOTE: Exceptions encountered when parsing the PB or handling the request # will be propagated back to the caller the same way as exceptions raised # by the actual API call. if environ.get('HTTP_TRANSFER_ENCODING') == 'chunked': # CherryPy concatenates all chunks when 'wsgi.input' is read but v3.2.2 # will not return even when all of the data in all chunks has been # read. See: https://bitbucket.org/cherrypy/cherrypy/issue/1131. wsgi_input = environ['wsgi.input'].read(2**32) else: wsgi_input = environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])) request.ParseFromString(wsgi_input) service = request.service_name() service_stub = apiproxy_stub_map.apiproxy.GetStub(service) if isinstance(service_stub, datastore_grpc_stub.DatastoreGrpcStub): # len(request.request()) is equivalent to calling ByteSize() on # deserialized request.request. if len(request.request()) > apiproxy_stub.MAX_REQUEST_SIZE: raise apiproxy_errors.RequestTooLargeError( apiproxy_stub.REQ_SIZE_EXCEEDS_LIMIT_MSG_TEMPLATE % ( service, request.method())) response = service_stub.MakeSyncCallForRemoteApi(request) else: if request.has_request_id(): request_id = request.request_id() environ['HTTP_HOST'] = self._balanced_address op = getattr(service_stub.request_data, 'register_request_id', None) if callable(op): op(environ, request_id) api_response = _execute_request(request).Encode() response.set_response(api_response) except Exception, e: if isinstance(e, apiproxy_errors.ApplicationError): level = logging.DEBUG application_error = response.mutable_application_error() application_error.set_code(e.application_error) application_error.set_detail(e.error_detail) else: # If the runtime instance is not Python, it won't be able to unpickle # the exception so use level that won't be ignored by default. level = logging.ERROR # Even if the runtime is Python, the exception may be unpicklable if # it requires importing a class blocked by the sandbox so just send # back the exception representation. # But due to our use of the remote API, at least some apiproxy errors # are generated in the Dev App Server main instance and not in the # language runtime and wrapping them causes different behavior from # prod so don't wrap them. if not isinstance(e, apiproxy_errors.Error): e = RuntimeError(repr(e)) # While not strictly necessary for ApplicationError, do this to limit # differences with remote_api:handler.py. response.set_exception(pickle.dumps(e)) logging.log(level, 'Exception while handling %s.%s()\n%s', request.service_name(), request.method(), traceback.format_exc())
def _RemoteSend(self, request, response, method, request_id=None): """Sends a request remotely to the datstore server. """ tag = self.project_id self._maybeSetDefaultAuthDomain() user = users.GetCurrentUser() if user != None: tag += ":" + user.email() tag += ":" + user.nickname() tag += ":" + user.auth_domain() api_request = remote_api_pb.Request() api_request.set_method(method) api_request.set_service_name("datastore_v3") api_request.set_request(request.Encode()) if request_id is not None: api_request.set_request_id(request_id) api_response = remote_api_pb.Response() retry_count = 0 max_retries = 3 location = self.__datastore_location while True: try: api_request.sendCommand(location, tag, api_response, 1, self.__is_encrypted, KEY_LOCATION, CERT_LOCATION) break except socket.error as socket_error: if socket_error.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH): backoff_ms = 500 * 3**retry_count # 0.5s, 1.5s, 4.5s retry_count += 1 if retry_count > max_retries: raise logging.warning( 'Failed to call {} method of Datastore ({}). Retry #{} in {}ms.' .format(method, socket_error, retry_count, backoff_ms)) time.sleep(float(backoff_ms) / 1000) location = get_random_lb() api_response = remote_api_pb.Response() continue if socket_error.errno == errno.ETIMEDOUT: raise apiproxy_errors.ApplicationError( datastore_pb.Error.TIMEOUT, 'Connection timed out when making datastore request') raise # AppScale: Interpret ProtocolBuffer.ProtocolBufferReturnError as # datastore_errors.InternalError except ProtocolBuffer.ProtocolBufferReturnError as e: raise datastore_errors.InternalError(e) if not api_response or not api_response.has_response(): raise datastore_errors.InternalError( 'No response from db server on %s requests.' % method) if api_response.has_application_error(): error_pb = api_response.application_error() logging.error(error_pb.detail()) raise apiproxy_errors.ApplicationError(error_pb.code(), error_pb.detail()) if api_response.has_exception(): raise api_response.exception() response.ParseFromString(api_response.response())
def add_tasks(self, project_id, service_id, version_id, add_requests): """ Makes a call to the TaskQueue service to enqueue tasks. Args: project_id: A string specifying the project ID. service_id: A string specifying the service ID. version_id: A string specifying the version ID. add_requests: A list of TaskQueueAddRequest messages. Raises: EnqueueError if unable to enqueue tasks. """ request = taskqueue_service_pb.TaskQueueBulkAddRequest() for add_request in add_requests: request.add_add_request().MergeFrom(add_request) api_request = remote_api_pb.Request() api_request.set_method('BulkAdd') api_request.set_service_name('taskqueue') api_request.set_request(request.Encode()) encoded_api_request = api_request.Encode() headers = { 'ProtocolBufferType': 'Request', 'AppData': project_id, 'Module': service_id, 'Version': version_id } api_response = None for location in self._locations: url = 'http://{}'.format(location) try: response = yield self._client.fetch(url, method='POST', body=encoded_api_request, headers=headers) except socket_error: # Try a different location if the load balancer is not available. continue except HTTPError as error: raise EnqueueError(str(error)) api_response = remote_api_pb.Response(response.body) break if api_response is None: raise EnqueueError('Unable to connect to any load balancers') if api_response.has_application_error(): error_pb = api_response.application_error() raise EnqueueError(error_pb.detail()) if api_response.has_exception(): raise EnqueueError(api_response.exception()) bulk_response = taskqueue_service_pb.TaskQueueBulkAddResponse( api_response.response()) if bulk_response.taskresult_size() != len(add_requests): raise EnqueueError('Unexpected number of task results') for task_result in bulk_response.taskresult_list(): if task_result.result( ) != taskqueue_service_pb.TaskQueueServiceError.OK: raise EnqueueError( 'Unable to enqueue task: {}'.format(task_result))