diff --git a/.gitignore b/.gitignore index f8af7d0..bda606c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,6 @@ venv .idea students/data.sqlite + +# vagrant +.vagrant \ No newline at end of file diff --git a/README.md b/README.md index 68042d4..7d76a44 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,127 @@ Custom APIs using Flask -======================================================== +===================== This repo contains a seed project to help you start building a microservice with custom API endpoints. +If this is your first time, do the following after you clone: +``` +# create a virtualenv environment +virtualenv venv + +# start virtualenv environment +source venv/bin/activate + +# install dependencies needed for your app +pip install -r requirements.txt + +# start the server +python requests/app/api.py + +# stop virtualenv environment +deactivate +``` + +For subsequent runs, simply do the following: +``` +# start virtualenv environment +source venv/bin/activate + +# start the server, ctrl-c if you want to stop server +python requests/app/api.py + +# Test 1: in separate console window, retrieve all students +http GET http://localhost:5000/students/ + +# Test 2: in separate console window, create a new student +http POST http://localhost:5000/students/ name=Armen + +# Test 3: in separate console window, retrieve a specific user +http GET http://localhost:5000/students/1 + +# Test 4: in separate console window, update an existing user +http PUT http://localhost:5000/students/1 name="Changed Name" + +# stop virtualenv environment when you're finished +deactivate +``` + +Challenge (due April 5th): +``` +1. Complete the `For subsequent runs` section above +2. Write a script which will test the 4 endpoints above (GET, POST, GET, PUT) using curl. +3. Write a script which will populate 30 records into your database. +``` + + +Challenge (due April 12th) + +1. Find a data set + +2. Create a SQL schema + +3. Write a script to extract and transform the data into an input file + +3. Implement REST POST endpoint to populate data into sqlite + +4. Write a script to load data into DB using curl from previous step + + + +Some interesting product feature ideas may be: + +Best Posts + +Worst Posts + +Most Controversial + +Most Commented + +Most/Least posts by author + +Top Site References + +**Don't forget to push your github branch on Mondays. + + +Instructions for Git Workflow + +``` +1. Git pull or Git clone repo which you plan to make changes to + +2. Create a branch using (`git checkout -b branchname`) + +3. Make the changes + +4. Commit the changes locally + +5. Push changes to remote repo (`git push -u origin branchname`) + +6. Create Pull Request and send to reviewers + +7. Address reviewer comments and merge Pull Request to master +``` + +Challenge (due April 17th) + +We discussed in class what and how to use Apache Bench (https://www.digitalocean.com/community/tutorials/how-to-use-apachebench-to-do-load-testing-on-an-arch-linux-vps). + +Your challenge for this week is to create a report in markdown format callled `load_test.md`. This report should contain a few benchmarks/scenarios which you can perform on your app. If you haven't gotten previous weeks challenge to work, then you can simply clone and run the seed github project for students as you did 3 weeks ago. + +Please provide an explanation for each load test scenario and your justification for why or how you came up with the different values for `-n` & `-c` flags. You can also try using `ngrok` after you document your tests for `localhost` and explain differences. + + +Challenge Due (April 25th) + +1. Implement a 404.html error page +``` +# todo: implement this template +@app.errorhandler(404) +def not_found(e): + return render_template('404.html') +``` + +2. Implement a `index.html` for your project and make it look pretty. Use `app/index.html` as an example. + +3. Get `vagrant up` working for your project diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..2f584a1 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,34 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-i386-vagrant-disk1.box" + config.vm.box = "precise64" + config.ssh.forward_agent = true + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + #config.vm.network "private_network", ip: "localhost" + config.vm.network :forwarded_port, host: 5000, guest: 5000 + + config.vm.provider :virtualbox do |vb, override| + + # The Virtualbox image + override.vm.box = "precise64" + override.vm.box_url = "http://files.vagrantup.com/precise64.box" + + # Port forwarding details + + # Flask + override.vm.network :forwarded_port, host: 5000, guest: 5000 + + # You can increase the default amount of memory used by your VM by + # adjusting this value below (in MB) and reprovisioning. + vb.customize ["modifyvm", :id, "--memory", "384"] + end + + # Setup web server + config.vm.provision "shell", path: 'install_script.sh', args: '/vagrant', privileged: false +end diff --git a/install_script.sh b/install_script.sh new file mode 100644 index 0000000..8791c5a --- /dev/null +++ b/install_script.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# install web server dependencies +sudo apt-get update +sudo apt-get -y install python python-virtualenv nginx supervisor + +# install application (source location in $1) +mkdir /home/vagrant/students +cp $1/requirements.txt /home/vagrant +cp -R $1/students/* /home/vagrant/students/ + +# create a virtualenv and install dependencies +virtualenv /home/vagrant/students/venv +source /home/vagrant/students/venv/bin/activate +sudo /home/vagrant/students/venv/bin/pip install -r /home/vagrant/requirements.txt + +# configure supervisor +sudo cp /vagrant/student.conf /etc/supervisor/conf.d/ +sudo mkdir /var/log/student +sudo supervisorctl reread +sudo supervisorctl update + +# configure nginx +sudo cp /vagrant/student.nginx /etc/nginx/sites-available/student +sudo rm -f /etc/nginx/sites-enabled/default +sudo ln -s /etc/nginx/sites-available/student /etc/nginx/sites-enabled/ +sudo service nginx restart + +echo Application deployed to http://localhost:5000/ diff --git a/requirements.txt b/requirements.txt index a8a331e..c44afd3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,5 @@ itsdangerous==0.24 python-dateutil==2.2 requests==2.3.0 six==1.7.3 +flask-bootstrap +gunicorn==19.1.1 \ No newline at end of file diff --git a/student.conf b/student.conf new file mode 100644 index 0000000..d5893ff --- /dev/null +++ b/student.conf @@ -0,0 +1,9 @@ +; supervisor configuration + +[program:students] +command=/home/vagrant/students/venv/bin/gunicorn -b 127.0.0.1:5000 -w 4 --chdir /home/vagrant/students --log-file - students:app +user=vagrant +autostart=true +autorestart=true +stderr_logfile=/var/log/student/stderr.log +stdout_logfile=/var/log/student/stdout.log diff --git a/student.nginx b/student.nginx new file mode 100644 index 0000000..10a2da0 --- /dev/null +++ b/student.nginx @@ -0,0 +1,21 @@ +# nginx reverse proxy configuration + +server { + listen 80; + server_name _; + access_log /var/log/nginx/student.access.log; + error_log /var/log/nginx/student.error.log; + location / { + proxy_pass http://127.0.0.1:5000; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /static { + alias /home/vagrant/students/static; + } + location /favicon.ico { + alias /home/vagrant/students/static/favicon.ico; + } +} diff --git a/students/app/api.py b/students/app/api.py index a87976b..cb32a47 100644 --- a/students/app/api.py +++ b/students/app/api.py @@ -1,11 +1,13 @@ import os -from flask import Flask, url_for, jsonify, request +from flask import Flask, url_for, jsonify, request, render_template +from flask.ext.bootstrap import Bootstrap from flask.ext.sqlalchemy import SQLAlchemy basedir = os.path.abspath(os.path.dirname(__file__)) db_path = os.path.join(basedir, '../data.sqlite') app = Flask(__name__) +bootstrap = Bootstrap(app) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + db_path db = SQLAlchemy(app) @@ -62,6 +64,16 @@ def edit_student(id): db.session.commit() return jsonify({}) +# todo: implement this template +@app.errorhandler(404) +def not_found(e): + return render_template('404.html') + +@app.route('/') +def index(): + highlight = {'min': 1, 'max': 2} + students = Student.query.all() + return render_template('index.html', students=students, highlight=highlight) if __name__ == '__main__': db.create_all() diff --git a/students/app/templates/index.html b/students/app/templates/index.html new file mode 100644 index 0000000..a5350e6 --- /dev/null +++ b/students/app/templates/index.html @@ -0,0 +1,37 @@ +{% extends 'bootstrap/base.html' %} +{% block title %}List of Students{% endblock %} + +{% block navbar %} + +{% endblock %} + +{% block content %} +
+

Student Info

+ + + + + + + {% for student in students %} + + + + + + + {% endfor %} +
IdName
+ {% set hl = student.id <= highlight['min'] %} + {% if hl %}{% endif %} + {{ student.id }} + {% if hl %}{% endif %} + {{ student.name }}
+
+{% endblock %} +