Facing API latency issue on EKS (FLASK server) - python

Hi we are facing an issue with python flask running on Kubernetes(EKS), we never faced this issue while running the same app on EC2 servers
The issue is while reading the incoming request payload. it is not re-producible and it happens once in 100 requests, sometimes it takes time ~1 to 5 sec, some times it is throwing an error at 20s, mentioned the error below
builtins:OSError cannot read from timed out object
Below is the code we are using
from flask import request, jsonify
from werkzeug.datastructures import ImmutableMultiDict
def request_parser():
'''
Request Parser will parse args and kwargs from request object
where args are ImmutableMultiDict, convert to_dict with flat=False,
and return only dict type
'''
try:
if request.method == "GET":
args = request.args
elif request.method in ("POST", "PUT"):
args = request.json or request.form
elif request.method == "DELETE":
args = request.args
if request.get_json(silent=True):
request.json.update(request.args.to_dict(flat=False))
args = request.json
else:
args = request.args
# Checking if the args is immutablemultidict
# if it then it is required to change to dict
# because of change in python3.
if isinstance(args, ImmutableMultiDict):
args = args.to_dict(flat=False)
return args
except Exception as e:
return {}
from the tracing, we noticed that all the requests that are taking time are being stuck at the line args = request.json or request.form, sometimes it takes more time, some times(very rarely) it times out with above-mentioned exception in the flask Kubernetes environment, if anyone has faced a similar issue please share your inputs.
We are running on EKS cluster version 1.23.x, latest control plane components.
Nodegroups : c6i.xlarge
Traffic flow : ALB -> Edge-stack-ingress -> pods

Related

need help on using arguments out the function decorator in flask

I want to use parameters outside of the decarator, i tried using it but getting below error
I am using flask API endpoints to give arg parameters such as source, destination and prot and protcol and SDC , DDC
In my casse when i use /t2e/ get method i have to use four parameters but when i use /junos/ then need to use 6 parametes , i dont want to repeat 4 parameters such as source, destination and prot and protcol. hence i tried use them above the method as below
Non working and getting error as
This typically means that you attempted to use functionality that needed
an active HTTP request. Consult the documentation on testing for
information about how to avoid this problem.
from flask import Flask, request, jsonify
from main.hunter_function import t2e, junos,
method = Flask(__name__)
args = request.args
source = args.get('source')
destination = args.get('destination')
port = args.get('port')
protocol = args.get('protocol')
#method.route('/t2e/', methods=['GET']) # is a decorator used to match URLs to view functions in Flask apps
def method_t2e():
logger.info("method is fetching t2e output")
output = t2e(source, destination, port, protocol)
return output
#method.route('/junos/', methods=['GET']) # is a decorator used to match URLs to view functions in Flask apps
def method_junos():
sdc = args.get('sdc')
ddc = args.get("ddc")
logger.info("method is fetching Junos output from link")
output = jun(source, destination, port, protocol,sdc,ddc)
return output
if __name__ == '__main__':
method.run(ssl_context="adhoc",host='0.0.0.0', port=5002, debug=True)
Here is working code:
#method.route('/t2e/', methods=['GET']) # is a decorator used to match URLs to view functions in Flask apps
def method_t2e():
args = request.args
source = args.get('source')
destination = args.get('destination')
port = args.get('port')
protocol = args.get('protocol')
logger.info("method is fetching t2e output")
output = t2e(source, destination, port, protocol)
return output
#method.route('/junos/', methods=['GET']) # is a decorator used to match URLs to view functions in Flask apps
def method_junos():
args = request.args
source = args.get('source')
sdc = args.get('sdc')
destination = args.get('destination')
ddc = args.get("ddc")
port = args.get('port')
protocol = args.get('protocol')
logger.info("method is fetching Junos output from link")
output = jun(source, destination, port, protocol,sdc,ddc)
return output
I don't want to repeat the parameters more than once
You could create a function which returns those to reduce code duplication such as
def get_args(args):
return args.get('source'), args.get('destination'), args.get('port'), args.get('protocol')
#method.route('/t2e/', methods=['GET'])
def method_t2e():
args = request.args
source, destination, port, protocol = get_args(args)
The issue with what you're trying to do is the request variable doesn't exist outside the call to your methods so in order to get them you need to pass the specific request for the method somewhere

Why is reqparse not understanding the POST request?

From what I've seen online, I can use reqparse from flask_restful to add data to my GET and POST requests.
Here is what I have:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
import pandas as pd
import ast
app = Flask(__name__)
api = Api(app)
class User(Resource):
def get(self):
return {'data': 'get'}, 200
def post(self):
parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument('userId', type=str)
args = parser.parse_args()
print(args)
return {'data': 'post'}, 200
class Game(Resource):
pass
api.add_resource(User, '/user')
api.add_resource(Game, '/game')
if __name__ == '__main__':
app.run()
I'm trying to send this POST request (using Postman):
http://127.0.0.1:5000/user?userId=hello
But I always get this error back:
{
"message": "The browser (or proxy) sent a request that this server could not understand."
}
I truly don't know what I'm doing wrong...
I still have not figured out how to fix reqparse. However, I managed to get the result desired another way.
Basically you can change the api.add_resource() to include variables which you can pass into the class path. Like: api.add_resource(User, 'user/<userID>') allows you to send a request like http://127.0.0.1:5000/user/kayer and have the variable userID be kayer.
The other way (to go back to my original question on how to use this type of request: http://127.0.0.1:5000/user?userID=kayer is to implement reuest.args.get('userID') and assign the return of that function to a variable.
Full code:
from flask import Flask, request
from flask_restful import Resource, Api, reqparse
import pandas as pd
import ast
app = Flask(__name__)
api = Api(app)
class User(Resource):
def get(self, userId):
a = request.args.get('name')
return {'data': {'a': userId, 'b': a}}, 200
def post(self):
pass
class Game(Resource):
pass
api.add_resource(User, '/user/<userId>')
api.add_resource(Game, '/game')
if __name__ == '__main__':
app.run()
I think the problem is that reqparse (or flask_restful itself), by default, is not parsing the query string (?userId=hello), when it matches the request URL with the endpoint.
From https://flask-restful.readthedocs.io/en/latest/reqparse.html#argument-locations:
By default, the RequestParser tries to parse values from flask.Request.values, and flask.Request.json.
Use the location argument to add_argument() to specify alternate locations to pull the values from. Any variable on the flask.Request can be used.
As instructed and from the example on that documentation, you can explicitly specify a location keyword param as location="args", where args refers to flask.Request.args, which means the:
The parsed URL parameters (the part in the URL after the question mark).
...which is exactly where userId is set when you call the endpoint.
It seems to work after adding the location="args" parameter:
def post(self):
parser = reqparse.RequestParser(bundle_errors=True)
parser.add_argument("userId", type=str, location="args") # <--------
args = parser.parse_args()
print(args)
return {"data": args}, 200
Postman:
cURL:
$ curl -XPOST http://localhost:5000/user?userId=hello
{
"data": {
"userId": "hello"
}
}
Tested with:
Flask 2.1.2
Flask-RESTful 0.3.9

What is the difference in configuring the end points using app.route() and api.add_resource() in a Python Flask application?

I am learning Python-Flask and found out that there are two ways to create endpoints in an application.
1. app.routing(/endpoint)
2. api.add_resource(CLASSNAME, endpoint)
Using app.routing() we can just add an endpoint over a method and invoke it. Using api.add_resource() we need to register the class name & the end points.
I've seen that the method names are given as get() & put() if you are using api.add_resource()
For ex:
app = Flask(__name__)
api = Api(app)
vehicles = []
class VehicleData(Resource):
parser = reqparse.RequestParser()
parser.add_argument('vehicle', type=str, required=True, help='name cannot be empty')
parser.add_argument('type', type=str, required=True, help='vehicle type cannot be empty')
parser.add_argument('wheels', type=int, required=True, help='number of wheels cannot be empty')
parser.add_argument('suv', type=bool, required=False, help='SUV or not can be empty')
def get(self, name):
vehicle = next(filter(lambda x: x['name'] == name, vehicles), None)
return {'vehicle': vehicle}, 200 if vehicle else 404
def post(self, name):
# data = request.get_json()
# sport.append({'sportname': data['sport_name'], 'team_size':data['team_size'], 'popularity':data['popularity']})
if next(filter(lambda x: x['name'] == name, vehicles), None) is not None:
print("in the IF BLOCK")
return {'message': 'The vehicle {n} already exists in the database'.format(n=name)}, 404
v_data = VehicleData.parser.parse_args()
vehicle = {'name': name, 'type':v_data['type'], 'vehicle': v_data['vehicle'], 'suv': v_data['suv'], 'wheels': v_data['wheels']}
vehicles.append(vehicle)
return vehicle, 201
def getallvehicles(self):
return {'vehicles': vehicles}
api.add_resource(VehicleData, '/addvehicle/<string:name>', '/getvehicle/<string:name>')
app.run(port=5000, debug=True)
If I submit the API http://127.0.0.1:5000/getvehicle with a GET http call, I am properly getting the data as per the logic given in the code. In the code I have only one GET & POST method. So Flask is automatically invoking the get() when I submit http://127.0.0.1:5000/getvehicle/<name> with a GET request.
What if I have a method name other than get()/post()/put() ?
For example, if I have a method getallvehicles() that returns all the vehicles i.e returns the list vehicles, how can I register it in api.register() ?
For example, I tried this:
api.add_resource(VehicleData, '/addvehicle/<string:name>', '/getvehicle/<string:name>', '/getallvehicles')
and submitted the API call 'http://127.0.0.1:5000/getallvehicles` with a GET request, I am facing an error:
File "/Users/bobby/PyCharmProjects/FlaskAPI/venv/lib/python3.7/site-packages/flask_restful/__init__.py", line 583, in dispatch_request
resp = meth(*args, **kwargs)
TypeError: get() missing 1 required positional argument: 'name'
Could anyone tell me what is the mistake I did here and how can I give an endpoint for getallvehicles() in the below line and map it to a GET http request:
api.add_resource(VehicleData, '/addvehicle/<string:name>', '/getvehicle/<string:name>', '/getallvehicles')
The difference between the 2 different ways is, that:
This one is "native" flask method, that you can use to wrap your functions with.
#app.routing('/endpoint')
This one is a part of the restfull_flask package and it does the things in a different way than the native flask way.
api.add_resource(CLASSNAME, endpoint)
You can do the same stuff with both ways, but if you use the rest_framework, then you should use the second method :)
For the rest of your question I am sure you will find answers in this documentation:
https://flask-restful.readthedocs.io/en/latest/

Flask swagger api.doc() requires positional argument but actually given

I just started to code in Flask swagger backend. When I tried to extract multiple paths and the argument parameters, an error occurs, says:
TypeError: get() missing 1 required positional argument: 'q'
But actually, my "get()" function has the positional argument 'q'. I may ask why this error occurs? That could be weird since I use
request.arg.get('q')
it works good and can returns the expected query argument.
The expected API paths is:
GET 127.0.0.1:8888/book/1?q=question20
and my code is here:
from flask import Flask, request
from flask_restplus import Resource, Api
app = Flask(__name__)
api = Api(app)
#api.route("/<string:resource>/<int:rid>")
#api.doc(params={'q': 'Your query here'})
class API(Resource):
def get(self, resource, rid, q):
return {"resource": resource,
"id": rid,
"query": q
}, 200
if __name__ == "__main__":
app.run(host='127.0.0.1', port=8888, debug=True)
I would appreciate if someone can help me solve this problem.
You can use api.parser() to document and capture query parameters
parser = api.parser()
parser.add_argument('q', type=str, help='Resource name', location='args')
#api.route("/<string:resource>/<int:rid>")
#api.doc(parser=parser)
class API(Resource):
def get(self, resource, rid):
args = parser.parse_args()
q = args['q']
return {"resource": resource,
"id": rid,
"q":q,
}, 200
This method is marked as deprecated https://flask-restplus.readthedocs.io/en/stable/parsing.html

Unable to send POST request from postman to API made using Flask

Hi I am new to writing web APIs in python. And my understanding of REST is limited
I have a simple Flask API that takes in a python dict {'pdf':pdf_as_bytes, 'filename':string}
The below is my server script:
#app.route("/predict", methods=["POST"])
def predict():
data = {"success": False}
if flask.request.method == "POST":
pdf = flask.request.files["pdf"].read()
filename = flask.request.files["filename"].read().decode("utf-8")
assert isinstance(filename, str), "Got {}".format(type(filename))
assert isinstance(pdf, bytes), "Got {}".format(type(pdf))
# further processing happens and returns a json
This works as intended when I write a python client as follows:
import requests
import os
ip = "localhost"
port = 8605
url = "http://{}:{}/predict".format(ip,port)
path_to_pdf = "./617339931.pdf"
with open(path_to_pdf, "rb") as f:
pdf = f.read() # pdf is a bytes
# the payload must have the following fields: "pdf": bytes, "filename": string object
payload = {"pdf":pdf,"filename":os.path.basename(path_to_pdf).split(".")[0]}
# post request
result = requests.post(url=url, files=payload).json()
# the resulting json always has a field called as success, which returns True or False accordingly
if result["success"] == True:
print(result["data"].keys())
But, When I send a request using Postman I get a 400 Error! Below is the screen shot of the error
I don't understand. How can I change my server code so that it works with Postman and also Python client programs
I just did the same thing, and I think it's because of the double quotes you are putting in key and value, try to take them out.

Categories

Resources