Writing (Micro)Services with Flask

Chris St. Pierre - @chris_st_pierre

Follow along:
stpierre.github.io/flask-microservices/

Clone:
https://github.com/stpierre/flask-microservices.git

Ensure that make and Python 2.7 are installed

Slack: #flask-microservices at usenix-lisa.slack.com

Need an invite? lisainvite.herokuapp.com

We have three hours to write a REST API that exposes uptime and iostat to the internet.

Flask

Flask-RESTFul


              make venv
          

The basics: uptime


              emacs solution/app.py
          

              % alias vi=emacs
% type vi
vi is aliased to `emacs'
          

              make test
          

              subprocess.check_output(["uptime"])
          

              make service
          

              curl http://localhost:5000/v1/uptime/
          

Always return a dict!


              return {"uptime": subprocess.check_output(["uptime"]).strip()}
          

              % curl http://localhost:5000/v1/uptime/
{"uptime": "20:16:57 up 14 days,  7:33, 25 users,  load average: 0.58, 0.25, 0.14"}
          

Query strings and error handling: iostat

Mac OS:


                iostat -d [-c <count>] [-w <wait>]
            

Linux:


                iostat -d <wait> <count>
            


              http://localhost:5000/v1/iostat/?count=4&wait=2
          

              wait = int(flask.request.args.get("wait", 1))
          

curl http://localhost:5000/v1/iostat/
curl http://localhost:5000/v1/iostat/\?count=2
curl http://localhost:5000/v1/iostat/\?count=5\&wait=1
          
"If you make something idiot-proof, someone will just make a better idiot."

curl http://localhost:5000/v1/iostat/\?count=-10
curl http://localhost:5000/v1/iostat/\?count=1.5
curl http://localhost:5000/v1/iostat/\?count=1\&wait=10
            

              restful.abort(400, msg="Try harder, moron")
          
  1. count or wait are not integers
  2. count or wait are less than 1
  3. wait is specified, but count is 1

curl http://localhost:5000/v1/iostat/\?count=1000\&wait=1000
          

A presentation design decision.


curl http://localhost:5000/v2/iostat/
          

@api.resource("/v1/uptime/", "/v2/uptime")
class UptimeV1(restful.Resource):
    ...
          

Introducing tasks

  • /v2/uptime/: no change!
  • /v2/iostat/: returns task object
  • /v2/task/: monitor and delete tasks

Celery: Distributed Task Queue


              emacs solution/tasks.py
          

              result = tasks.iostat.delay(count, wait)
          

return flask.make_response(
    flask.jsonify({"task_id": result.id,
                   "links": [
                       {"rel": "task",
                        "href": api.url_for(TaskV2,
                                            task_id=result.id)}]}),
    201)
          

              make celery
          

% curl http://localhost:5000/v2/iostat/
{
  "links": [
    {
      "href": "/v2/task/1800799b-bfa1-4e14-9552-02d2dd82f01a",
      "rel": "task"
    }
  ],
  "task_id": "1800799b-bfa1-4e14-9552-02d2dd82f01a"
}
          
  • GET /v2/task/<task_id>/: poll a task
  • DELETE /v2/task/<task_id>/: cancel a task

@api.resource("/v2/task/<string:task_id>")
class TaskV2(restful.Resource):
    def get(self, task_id):
        ...
          

              task = tasks.app.AsyncResult(task_id)
          
  • If task.state == "PENDING", the task doesn't exist.
  • task.ready() tells you if the task is done running and task.result is meaningful.
  • If task.result is an exception, the task failed.
  • task_id
  • state
  • result, only if the task is ready
  • links

@api.resource("/v2/task/<string:task_id>")
class TaskV2(restful.Resource):
    def get(self, task_id):
        ...

    def delete(self, task_id):
        ...
          

              task.revoke()
          

              return flask.make_response("", 204)
          

Extra credit: API v3

  • Create (POST), read (GET), update (PUT/PATCH), and delete (DELETE) users in a database
  • Database can be created with make v3-database
  • User records have four fields: username, password, admin, fullname

Extra credit: API v3

  • Use users in the database for authentication
  • Unauthenticated users can only access /v3/
  • Non-admin users cannot create, update, or delete users, but can do everything else
  • Admin users can do everything
  • v1 and v2 APIs do not need authentication

Extra credit: API v3

  • Unit tests are already provided
  • One solution is in SOLUTION_DO_NOT_PEEK/