Relax!



Relax!

0 0


relax


On Github jeanphix / relax

Relax!

Leave me alone! I'm deploying...

A basic web application:

from flask import Flask
from flask.views import MethodView


app = Flask(__name__)


class HelloView(MethodView):

    def get(self, name):
        return "Hello %s!" % name, 200


app.add_url_rule('/<name>/', HelloView.as_view('hello'),
    _methods=['GET'])

Application coverage:

import unittest


from app import app, HelloView


class HelloViewTest(unittest.TestCase):

    def test_get_bob(self):
        self.assertEqual(HelloView().get('Bob'), ('Hello Bob!', 200))

    def test_http_get_bob(self):
        client = app.test_client()
        response = client.get('/bob/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, 'Hello Bob!')

Remote production setup:

git init
git add .
git commit -m 'Bootstrap my app'
git remote add production owner@my.production.server:repository.git

Deployment script:

from fabric.state import env
from fabric.api import cd, task

# ....

@task
def checkout(revision):
    with cd(env.repo_path):
        run('git checkout %s -f' % revision)

@task
def deploy(remote, revision='HEAD'):
    push(revision)
    checkout(commit)
    restart()

@task
def push(remote):
    local('git push %s master' % remote)

@task
def restart():
    worktree('sudo /usr/bin/systemctl restart ${APP}')

Ok, I'm done, let's deploy!

fab deploy:remote=production

deployment tasks are not covered

Testing a deployment task:

import unitest
from fabric.state import env
from fabfile import checkout


class CheckoutTest(unittest.TestCase):

    def setUp(self):
        # start the mocked target environment
        env.host = 'my.mocked.host'
        env.user = 'owner'

    def tearDown(self):
        # destroy the mocked target environment

    def test_head(self):
        checkout()
        # assert head has been checked out

Now, my application code and my deployment tasks are covered,

I should be able to deploy safely!

I'm absolutely not sure that the application will run properly over production env.

Functional tests should be written using a real HTTP client:

import unittest

from app import app, HelloView


class HelloViewTest(unittest.TestCase):

    def test_http_get_bob(self):
        client = app.test_client()
        response = client.get('/bob/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, 'Hello Bob!')
import unittest
import requests

from app import app, HelloView


class HelloViewTest(unittest.TestCase):

    def setUp(self):
        # start my app in a dedicated thread

    def test_http_get_bob(self):
        response = requests.get('localhost:5000/bob/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, 'Hello Bob!')

Then they can be run over mocked production:

import unittest
import requests

from app import app, HelloView


class HelloViewTest(unittest.TestCase):

    def setUp(self):
        # start my app in mocked production env

    def test_http_get_bob(self):
        response = requests.get('localhost:5000/bob/')
        self.assertEqual(response.status_code, 200)
        self.assertEqual(response.data, 'Hello Bob!')

How to mock production setup?

  • use provisioners (chef, puppet, salt...)
  • real instance(s)
  • virtualization (qemu, virtualbox...)

The missing lib.

Use case:

Scale:

from my_setup import Web


def scale(load_balancer, provisioner):
    web = Web()
    web.start()
    provisioner.provision(web)
    load_balancer.register(web)
    return web


new_web_instance = scale(load_balancer_instance, provisioner_instance)

Scaling tests:

import unittest

from my_setup import Provisioner, LoadBalancer, scale


class ScaleTest(unittest.TestCase):

    def test_provision(self):
        provisioner = Provisioner()
        load_balancer = LoadBalancer()
        web = scale(load_balancer, provisioner)
        # assert web has been provisioned

    def test_registration(self):
        provisioner = Provisioner()
        load_balancer = LoadBalancer()
        web = scale(load_balancer, provisioner)
        # assert web has been registered within the load balancer

Conclusion.

jeanphix, hacker @birdback