Cars monitoring client/server application using Python Twisted

January 23, 2011

This article describes how to use Twisted to build a client/server cars monitoring system. We are going to focus on the client/server communication.

The client and server source code can be retrieved using Git:

git clone https://github.com/laurentluce/twisted-examples.git

Overview

Some researchers invented a system capable of monitoring cars and detecting the brand and color of a car. We are in charge of building a client/server software solution to retrieve the list of cars from different monitoring locations.

First element is a server used to monitor the cars and listen for clients connections to reply with the list of cars. Second element is the client retrieving the list of cars from the servers. We will use the Deferred feature to handle completion and failures callbacks.

Twisted is an asynchronous networking framework. It uses an event loop called the reactor. When this loop detects an event, it uses callbacks to report those events. Events include: connection made, data received, connection lost…

Server

The server listens for connection and write the cars data when a connection is initiated.

First, we have our server application class. The constructor takes care of the following:

  • Logging facility initialization.
  • Cars list initialization.
  • Create a server protocol factory. This factory produces a protocol instance for each connection.
  • Start a thread monitoring cars.

The class has a method named “listen” to start listening for new TCP connections on a specific host and port.

class TrafficServer(object):
    """
    Server main class
    """
    def __init__(self):
        """
        Constructor
        """
        # init logging facility: log to client.log
        logging.basicConfig(filename='server.log', level=logging.DEBUG)
        # cars list
        self.cars = []
        # server listening interface
        self.interface = 'server1.monitoring.com'
        # server port number
        self.port = 8000
        # Factory class for connections
        self.factory = TrafficFactory(self.cars)
        # Thread monitoring for new cars
        self.watchcars = WatchCars(self.cars)
        self.watchcars.start()
 
    def listen(self):
        """
        Call reactor's listen to listen for client's connections
        """
        port = reactor.listenTCP(self.port or 0, self.factory,
            interface=self.interface)

This is our server factory class creating protocol instances each time a connection is made. We pass the list of cars so it can be accessed using the factory in the protocol instance which we are going to see next.

class TrafficFactory(ServerFactory):
    """
    Factory to create protocol instances.
    """
        
    protocol = TrafficProtocol

    def __init__(self, cars):
        """
        Constructor.

        @param cars cars list
        """
        logging.debug('Traffic factory init')
        self.cars = cars

Next is the protocol class itself. The Protocol class implements connectionMade() which is called when a new connection is made. We are going to use this callback method to write the cars data to the client.

class TrafficProtocol(Protocol):
    """
    Protocol class to handle data between the client and the server.
    """

    def connectionMade(self):
        """
        Callback when a connection is made. Write cars data to the client then
        close the connection.
        """
        logging.debug('Connection made')
        data = '.'.join(self.factory.cars)
        self.transport.write(data)
        self.transport.loseConnection()

Here is the flow on the server side:

The WatchCars thread watches for new cars and append them to the cars list. Assume that get_next_car() is a blocking call returning the cars one by one.

class WatchCars(Thread):
    """
    Thread monitoring the cars.
    """

    def __init__(self, cars):
        """
        Constructor.

        @param cars cars list
        """
        Thread.__init__(self)
        self.cars = cars
    
    def run(self):
        """
        Thread run. Get new cars and add them to the cars list.
        """
        while True:
            t, brand, color = get_next_car()
            self.cars.append('%s:%s:%s' % (t, brand, color))

Finally, we have a simple main() function creating an instance of the server and starting the reactor loop.

def main():
    server = TrafficServer()
    server.listen()
    reactor.run()

if __name__ == '__main__':
    main()

Let’s start the server alone (server.py) and look at the logging output:

python server.py &

cat server.log
DEBUG:root:Traffic server init
DEBUG:root:Traffic factory init
DEBUG:root:Watch cars thread init
DEBUG:root:Watch cars thread run
DEBUG:root:Traffic server listen

We can see the server initializing the factory to instantiate protocol objects each time a connection is made. The server is listening for connections from the client.

Client

The client retrieves the list of cars from the different servers.

First is our client class doing the following:

  • Logging facility initialization.
  • Initialize the Deferred object to handle callbacks and failures.
  • Create a client protocol factory. This factory produces instances of our protocol each time a connection is made.
  • Cars list initialization.
  • Servers addresses list initialization.
class TrafficClient(object):
    """
    Client
    """
    def __init__(self):
        """
        Constructor
        """
        # init logging facility: log to client.log
        logging.basicConfig(filename='client.log', level=logging.DEBUG)
        # init deferred object to handle callbacks and failures
        self.deferred = defer.Deferred()
        # init factory to create protocol instances
        self.factory = TrafficClientFactory(d)
        # list of cars
        self.cars = []
        # keep track of servers replying so we know when the overall work
        # is finished
        self.addr_count = 0
        # list of servers to get cars list from
        self.addresses = [('server1.monitoring.com', 8000),
            ('server2.monitoring.com', 8000)]
        logging.debug('Init traffic client')
    ... 

Let’s look at the methods of the client class.

First is get_cars() which retrieves the list of cars from a server. It uses the reactor connectTCP() method to initiate a connection to the server. We will see later how we detect that we received data from he server. The deferred object allow us to register success and failure callbacks instead of handling the exception ourselves. We are going to register some callbacks in the main loop.

    def get_cars(self, host, port):
        """
        Connect to server to retrieve list of cars

        @param host server's hostname
        @param port server's port
        """
        reactor.connectTCP(host, port, self.factory)

Next is the main loop used to retrieve the list of cars from all the servers. We also register callbacks for when the list is returned and also to handle errors. The addCallbacks() method allow us to specify both. We also register a done method to be called no matter what happens. We are going to see those callback methods next.

    def update_cars(self):
        """
        Retrieve list of cars from all servers. Set callbacks to handle
        success and failure.
        """
        for address in self.addresses:
            host, port = address
            self.get_cars(host, port)
            self.deferred.addCallbacks(self.got_cars, self.get_cars_failed)
            self.deferred.addBoth(self.cars_done)

The method got_cars() is called when we are done receiving the data from the server. It is called by the protocol instance handling the data between the client and the server. We are going to see the factory and the protocol classes later.

    def got_cars(self, cars):
        """
        Callback when cars retrieval is successful

        @param cars data returned by server
        """
        logging.debug('Got cars: %s' % cars)
        self.cars.extend(cars)

The method get_cars_failed() is called when an error happens in the reactor loop.

    def get_cars_failed(self, err):
        """
        Callback when retrieval from server failed. Log error.

        @param err server error
        """
        logging.debug('Get cars failed: %s' % err)

The method cars_done() is called when all servers cars list have been retrieved. We also tell the reactor to stop.

    def cars_done(self, cars):
        """
        Callback when retrieval operation is finished for all servers.
        Log cars list and stop Twisted reactor loop which is listening to events
        """
        self.addr_count += 1
        if self.addr_count == len(addresses):
            logging.debug('Cars done: %s' % self.cars)
            reactor.stop()

Next is our protocol class to handle the data between the client and the server. We need to specify a method to be called when some data is received. We also specify a method to be called when the connection is lost. This happens normally when the server closes the connection after sending the list of cars.

class TrafficProtocol(Protocol):
    """
    Protocol class to handle data between the client and the server.
    """

    data = ''

    def dataReceived(self, data):
        """
        Callback when some data is received from server.

        @param data data received from server
        """
        logging.debug('Data received: %s' % data)
        self.data += data

    def connectionLost(self, reason):
        """
        Callback when connection is lost with server. At that point, the
        cars have been receieved.

        @param reason failure object
        """
        logging.debug('Connection lost: %s' % reason)
        self.cars = []
        for c in self.data.split('.'):
            self.cars.append(c)
        self.carsReceived(self.cars)

    def carsReceived(self, cars):
        """
        Called when the cars data are received.

        @param cars data received from the server
        """
        self.factory.get_cars_finished(cars)

We need to create a factory class to produce protocol instances. The method get_cars_finished() is called by the protocol instance when the connection is lost with the server. We also define clientConnectionFailed() to handle connection errors. Note how we use the deferred callbacks methods registered by the client class.

class TrafficClientFactory(ClientFactory):
    """
    Factory to create protocol instances
    """

    protocol = TrafficProtocol

    def __init__(self, deferred):
        """
        Constructor.

        @param deferred callbacks to handle completion and failures
        """
        self.deferred = deferred

    def get_cars_finished(self, cars):
        """
        Callback when the cars data is retrieved from the server successfully

        @param cars data received from the server
        """
        if self.deferred:
            d, self.deferred = self.deferred, None
            d.callback(cars)

    def clientConnectionFailed(self, connector, reason):
        """
        Callback when connection fails

        @param connector connection object.
        @param reason failure object
        """
        if self.deferred:
            d, self.deferred = self.deferred, None
            d.errback(reason)

Here is the flow on the client side:

Our simple main() function instantiates a client object and starts the reactor loop.

def main():
    client = TrafficClient()
    client.update_cars()

    reactor.run()

if __name__ == '__main__':
    main()

Let’s start the client alone (client.py) and look at the logging output:

python client.py &

cat client.log
DEBUG:root:Traffic client init
DEBUG:root:Traffic client factory init: <Deferred at 0x21945a8>
DEBUG:root:Update cars
DEBUG:root:Get cars: server1.monitoring.com - 8000
DEBUG:root:Get cars: server2.monitoring.com - 8000
DEBUG:root:Client connection failed: <twisted.internet.tcp.Connector instance at 0x2a28638> - [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionRefusedError'>: Connection was refused by other side: 111: Connection refused.
]
DEBUG:root:Get cars failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionRefusedError'>: Connection was refused by other side: 111: Connection refused.
]
DEBUG:root:Client connection failed: <twisted.internet.tcp.Connector instance at 0x2a28668> - [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionRefusedError'>: Connection was refused by other side: 111: Connection refused.
]
DEBUG:root:Get cars failed: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionRefusedError'>: Connection was refused by other side: 111: Connection refused.
]
DEBUG:root:Cars done: []

The client tries to connect to server1.monitoring.com and server2.monitoring.com. clientConnectionFailed() is called because there is no server listening. This is expected behavior. This results in calling get_cars_failed() followed by cars_done() as it is the callbacks chain we define for the Deferred object.

Testing

Let’s start 2 servers and 1 client and see what happens:

python server.py &

cat server.log
DEBUG:root:Traffic server init
DEBUG:root:Traffic factory init
DEBUG:root:Watch cars thread init
DEBUG:root:Watch cars thread run
DEBUG:root:Traffic server listen
DEBUG:root:Connection made

On the server side, we can see that a connection is made from the client. The server writes data to the client and closes the connection.

python client.py &

cat client.log
DEBUG:root:Traffic client init
DEBUG:root:Traffic client factory init: <Deferred at 0x24975a8>
DEBUG:root:Update cars
DEBUG:root:Get cars: server1.monitoring.com - 8000
DEBUG:root:Get cars: server2.monitoring.com - 8000
DEBUG:root:Data received: 97264836:peugeot:red
DEBUG:root:Connection lost: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]
DEBUG:root:Cars received: ['97264836:peugeot:red']
DEBUG:root:Get cars finished: ['97264836:peugeot:red']
DEBUG:root:Got cars: ['97264836:peugeot:red']
DEBUG:root:Cars done: ['97264836:peugeot:red', '97264846:renault:green']
DEBUG:root:Data received: 97264836:renault:green
DEBUG:root:Connection lost: [Failure instance: Traceback (failure with no frames): <class 'twisted.internet.error.ConnectionDone'>: Connection was closed cleanly.
]
DEBUG:root:Cars received: ['97264846:renault:green']
DEBUG:root:Get cars finished: ['97264846:renault:green']
DEBUG:root:Got cars: ['97264846:renault:green']
DEBUG:root:Cars done: ['97264836:peugeot:red', '97264846:renault:green']

On the client side, 2 connections are made. 1 to server1.monitoring.com and 1 to server2.monitoring.com. ’97264836:peugeot:red’ is received from server1 and ’97264846:renault:green’ is received from server2.

That’s it for now. I hope you enjoyed this article. Please write a comment if you have any feedback.

OpenStack Nova nova.sh script explained

January 14, 2011

Update 11/24/2011: Updated article based on the latest nova.sh script.

This article describes the internals of the script nova.sh used to get the OpenStack Nova source code, install it and run it. Nova is a cloud computing fabric controller, the main part of an IaaS system.

The script can be retrieved using Git:

git clone https://github.com/vishvananda/novascript.git

Arguments

The script takes 1 mandatory argument and 2 optional arguments:

  • command: “branch”, “install”, “run”, “terminate”, “clean”, “scrub”.
  • source branch (branch command only): default to “lp:nova” which is the location of the source code on Launchpad.
  • install directory: default to “nova”

Note: You will need to use sudo to run the script.

Initialization

The arguments are grabbed from the command line or set to their defaults:

CMD=$1
if [ "$CMD" = "branch" ]; then
    SOURCE_BRANCH=${2:-lp:nova}
    DIRNAME=${3:-nova}
else
    DIRNAME=${2:-nova}
fi

By default, sqlite will be used but you can use MySQL instead by setting the env variable USE_MYSQL and MYSQL_PASS.

USE_MYSQL=${USE_MYSQL:-0}
MYSQL_PASS=${MYSQL_PASS:-nova}

Next is the interface used as the public interface and the VLAN interface in the nova configuration file.

INTERFACE=${INTERFACE:-eth0}

Floating IP addresses are used for HA. One VM can grab a floating IP address as it is taking over.

FLOATING_RANGE=${FLOATING_RANGE:-10.6.0.0/27}

Fixed IP addresses are attached to the different interfaces.

FIXED_RANGE=${FIXED_RANGE:-10.0.0.0/24}

You can set the path where instances data are stored:

INSTANCES_PATH=${INSTANCES_PATH:-$NOVA_DIR/instances}

You can also force the script to run some unit tests (Python unit tests) when you use the command “run”. For example, it will test the api, authentication, compute, network modules and much more. You can take a look at the folder nova/tests/ to see all the tests.

TEST=${TEST:-0}

LDAP can be used for authentication. It is not used by default. The database is used by default to store authentication data.

USE_LDAP=${USE_LDAP:-0}

OpenDJ can be used instead of OpenLDAP when LDAP is used. OpenDJ is a new LDAPv3 compliant directory service, developed for the Java platform, providing a high performance, highly available and secure store for the identities managed by enterprises.

USE_OPENDJ=${USE_OPENDJ:-0}

IPv6 support can be enabled:

USE_IPV6=${USE_IPV6:-0}

Nova has support for libvirt and you can set LIBVIRT_TYPE to something if you don’t like the default qemu. You can set it to “uml” and a different libvirt XML template will be used. Libvirt is a virtualization API.

LIBVIRT_TYPE=${LIBVIRT_TYPE:-qemu}

Next is the network manager type. It defaults to VlanManager where a host-managed VLAN will be created for each project. Other types are FlatManager, FlatDHCPManager. See Network Manager Documentation for more details.

NET_MAN=${NET_MAN:-VlanManager}

In case you are using FlatDHCP on multiple hosts, you need to set the env variable FLAT_INTERFACE to a network interface with no defined IP.

FLAT_INTERFACE=ethx

The first network interface IP address is grabbed using the ifconfig command. It is explained in the script that if you have more than 1 network interfaces then you should set the environment variable HOST_IP.

if [ ! -n "$HOST_IP" ]; then
HOST_IP=`LC_ALL=C ifconfig  | grep -m 1 'inet addr:'| cut -d: -f2 | awk '{print $1}'`
fi

The connection to the database is defined the following way. It will be MySQL or sqlite based on your choice.

if [ "$USE_MYSQL" == 1 ]; then
    SQL_CONN=mysql://root:$MYSQL_PASS@localhost/nova
else
    SQL_CONN=sqlite:///$NOVA_DIR/nova.sqlite
fi

The authentication driver is set based on your choice: LDAP or not. If LDAP is not selected (by default), it will use the database to store the authentication data.

if [ "$USE_LDAP" == 1 ]; then
    AUTH=ldapdriver.LdapDriver
else
    AUTH=dbdriver.DbDriver
fi

Branch command

This command installs Bazaar (bzr) which is a distributed version control system, initializes the repository, retrieves the latest source code for Nova and places it in the Nova folder you just defined It also creates the “instances” folder and “networks” folders.

if [ "$CMD" == "branch" ]; then
    sudo apt-get install -y bzr
    if [ ! -e "$DIR/.bzr" ]; then
        bzr init-repo $DIR
    fi
    rm -rf $NOVA_DIR
    bzr branch $SOURCE_BRANCH $NOVA_DIR
    cd $NOVA_DIR
    mkdir -p $NOVA_DIR/instances
    mkdir -p $NOVA_DIR/networks
    exit
fi

LXC setup

The libvirt LXC driver manages “Linux Containers”. Containers are sets of processes with private namespaces which can (but don’t always) look like separate machines, but do not have their own OS. If you use “lxc” for the libvirt type, some cgroups controllers need to be mounted on the host OS.

has_fsmp() {
  # has_fsmp(mountpoint,file): does file have an fstab entry for mountpoint
  awk '$1 !~ /#/ && $2 == mp { e=1; } ; END { exit(!e); }' "mp=$1" "$2" ;
}

function lxc_setup() {
  local mntline cmd=""
  mntline="none /cgroups cgroup cpuacct,memory,devices,cpu,freezer,blkio 0 0"
  has_fsmp "/cgroups" /etc/fstab ||
     cmd="$cmd && mkdir -p /cgroups && echo '$mntline' >> /etc/fstab"
  has_fsmp "/cgroups" /proc/mounts ||
     cmd="$cmd && mount /cgroups"

  [ -z "$cmd" ] && return 0
  sudo sh -c ": $cmd"
}

[ "$LIBVIRT_TYPE" != "lxc" ] || lxc_setup || fail "failed to setup lxc"

Install command

The following Debian packages are installed if not already installed:

  • python-software-properties: This software provides an abstraction of the used apt repositories. It allows you to easily manage your distribution and independent software vendor software sources.
  • dnsmasq-base: A small caching DNS proxy and DHCP/TFTP server.
  • kpartx: create device mappings for partitions.
  • kvm: Full virtualization on x86 hardware.
  • gawk: a pattern scanning and processing language.
  • iptables: administration tools for packet filtering and NAT.
  • ebtables: Ethernet bridge frame table administration.
  • user-mode-linux: User-mode Linux (kernel).
  • libvirt-bin: the programs for the libvirt library.
  • screen: terminal multiplexor with VT100/ANSI terminal emulation.
  • euca2ools: managing cloud instances for Eucalyptus.
  • vlan: user mode programs to enable VLANs on your ethernet devices.
  • curl: Get a file from an HTTP, HTTPS or FTP server.
  • rabbitmq-server: An AMQP server written in Erlang.
  • lvm2: The Linux Logical Volume Manager.
  • iscsitarget: iSCSI Enterprise Target userland tools.
  • open-iscsi: High performance, transport independent iSCSI implementation.
  • socat: multipurpose relay for bidirectional data transfer.
  • unzip: De-archiver for .zip files.
  • glance: The Glance project provides an image registration and discovery service (Parallax) and an image delivery service (Teller).
  • radvd: Router Advertisement Daemon.
  • python-twisted: Event-based framework for internet applications.
  • python-sqlalchemy: SQL toolkit and Object Relational Mapper for Python.
  • python-suds: Lightweight SOAP client for Python.
  • python-lockfile: file locking library for Python.
  • python-mox: a mock object framework for Python.
  • python-lxml: pythonic binding for the libxml2 and libxslt libraries.
  • python-kombu: AMQP Messaging Framework for Python.
  • python-greenlet: Lightweight in-process concurrent programming.
  • python-carrot: An AMQP messaging queue framework.
  • python-migrate: Database schema migration for SQLAlchemy.
  • python-eventlet: Eventlet is a concurrent networking library for Python.
  • python-gflags: Python implementation of the Google command line flags module.
  • python-novaclient: client library for OpenStack Compute API.
  • python-ipy: Python module for handling IPv4 and IPv6 addresses and networks.
  • python-cheetah: text-based template engine and Python code generator.
  • python-libvirt: libvirt Python bindings.
  • python-libxml2: Python bindings for the GNOME XML library.
  • python-routes: Routing Recognition and Generation Tools.
  • python-paste: Tools for using a Web Server Gateway Interface stack.
  • python-netaddr: manipulation of various common network address notations.
  • python-tempita: very small text templating language.
  • python-pastedeploy: Load, configure, and compose WSGI applications and servers.
  • python-glance: OpenStack Image Registry and Delivery Service.

The script also adds an APT repository: “ppa:nova-core/trunk” which contains some patched versions of some of the packages above.

The modules kvm and ndb are loaded. iscsitarget and libvirt-bin are restarted and a test image is downloaded and uncompressed.

If you enable IPv6 support, radvd will be installed, IPv6 forwarding will be enabled and router advertisement messages will be ignored.

If you chose to use MySQL, the root password is set for you based on the environment variable MYSQL_PASS and the following 2 packages are installed: mysql-server and python-mysqldb.

if [ "$CMD" == "install" ]; then
    sudo apt-get install -y python-software-properties
    sudo add-apt-repository ppa:nova-core/trunk
    sudo apt-get update
    sudo apt-get install -y dnsmasq-base kpartx kvm gawk iptables ebtables
    sudo apt-get install -y user-mode-linux kvm libvirt-bin
    # Bypass  RabbitMQ "OK" dialog
    echo "rabbitmq-server rabbitmq-server/upgrade_previous note" | sudo debconf-set-selections
    sudo apt-get install -y screen euca2ools vlan curl rabbitmq-server
    sudo apt-get install -y lvm2 iscsitarget open-iscsi
    sudo apt-get install -y socat unzip glance
    echo "ISCSITARGET_ENABLE=true" | sudo tee /etc/default/iscsitarget
    sudo /etc/init.d/iscsitarget restart
    sudo modprobe kvm
    sudo /etc/init.d/libvirt-bin restart
    sudo modprobe ndb
    sudo apt-get install -y python-mox python-lxml python-kombu python-paste
    sudo apt-get install -y python-migrate python-gflags python-greenlet
    sudo apt-get install -y python-libvirt python-libxml2 python-routes
    sudo apt-get install -y python-netaddr python-pastedeploy python-eventlet
    sudo apt-get install -y python-novaclient python-glance python-cheetah
    sudo apt-get install -y python-carrot python-tempita python-sqlalchemy
    sudo apt-get install -y python-suds python-lockfile python-netaddr
    
    if [ "$USE_IPV6" == 1 ]; then
        sudo apt-get install -y radvd
        sudo bash -c "echo 1 > /proc/sys/net/ipv6/conf/all/forwarding"
        sudo bash -c "echo 0 > /proc/sys/net/ipv6/conf/all/accept_ra"
    fi

    if [ "$USE_MYSQL" == 1 ]; then
        cat <<MYSQL_PRESEED | debconf-set-selections
mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
mysql-server-5.1 mysql-server/root_password_again password $MYSQL_PASS
mysql-server-5.1 mysql-server/start_on_boot boolean true
MYSQL_PRESEED
        apt-get install -y mysql-server python-mysqldb
    fi
    exit
fi

Run command

A lot is happening in this section. First is

A new screen is started in detached mode with the session name specified in the environment variable SCREEN_NAME. The following code also checks if a screen with the same session name already exists and asks the user to kill it if it is the case. Screen is a full-screen window manager that multiplexes a physical terminal between several processes.

# check for existing screen, exit if present
  found=$(screen -ls | awk '-F\t' '$2 ~ m {print $2}' "m=[0-9]+[.]$SCREEN_NAME")
  if [ -n "$found" ]; then
    {
    echo "screen named '$SCREEN_NAME' already exists!"
    echo " kill it with: screen -r '$SCREEN_NAME' -x -X quit"
    echo " attach to it with: screen -d -r '$SCREEN_NAME'"
    exit 1;
    } 2>&1
  fi
  screen -d -m -S $SCREEN_NAME -t nova
  sleep 1
  if [ "$SCREEN_STATUS" != "0" ]; then
    screen -r "$SCREEN_NAME" -X hardstatus alwayslastline "%-Lw%{= BW}%50>%n%f* %t%{-}%+Lw%< %= %H"
  fi

Based on the environment variables set above, the script writes the nova flags to nova.conf and creates the nova folder in /etc/.

cat >$NOVA_DIR/bin/nova.conf << NOVA_CONF_EOF
--verbose
--nodaemon
--dhcpbridge_flagfile=$NOVA_DIR/bin/nova.conf
--network_manager=nova.network.manager.$NET_MAN
--my_ip=$HOST_IP
--public_interface=$INTERFACE
--vlan_interface=$INTERFACE
--sql_connection=$SQL_CONN
--auth_driver=nova.auth.$AUTH
--libvirt_type=$LIBVIRT_TYPE
--fixed_range=$FIXED_RANGE
--lock_path=$LOCK_PATH
--instances_path=$INSTANCES_PATH
--flat_network_bridge=br100
NOVA_CONF_EOF

if [ -n "$FLAT_INTERFACE" ]; then
    echo "--flat_interface=$FLAT_INTERFACE" >>$NOVA_DIR/bin/nova.conf
fi

if [ "$USE_IPV6" == 1 ]; then
    echo "--use_ipv6" >>$NOVA_DIR/bin/nova.conf
fi

Next, all dnsmasq processes are killed: DNS cache proxy + DHCP server.

killall dnsmasq

In case IPv6 support is enabled, radvd is killed:

if [ "$USE_IPV6" == 1 ]; then
   killall radvd
fi

The script recreates the database “nova”.

if [ "$USE_MYSQL" == 1 ]; then
    mysql -p$MYSQL_PASS -e 'DROP DATABASE nova;'
    mysql -p$MYSQL_PASS -e 'CREATE DATABASE nova;'
else
    rm $NOVA_DIR/nova.sqlite
fi

If you decided to use LDAP, OpenLDAP or OpenDJ needs to be configured:

if [ "$USE_LDAP" == 1 ]; then
    if [ "$USE_OPENDJ" == 1 ]; then
        echo '--ldap_user_dn=cn=Directory Manager' >> \
            /etc/nova/nova-manage.conf
        sudo $NOVA_DIR/nova/auth/opendj.sh
    else
        sudo $NOVA_DIR/nova/auth/slap.sh
    fi
fi

The script also recreates the instances and networks folders.

rm -rf $NOVA_DIR/instances
mkdir -p $NOVA_DIR/instances
rm -rf $NOVA_DIR/networks
mkdir -p $NOVA_DIR/networks

If test mode is enabled (see above), the unit tests are run:

if [ "$TEST" == 1 ]; then
    cd $NOVA_DIR
    python $NOVA_DIR/run_tests.py
    cd $DIR
fi

A new database is created

$NOVA_DIR/bin/nova-manage db sync

A new admin user is added:

$NOVA_DIR/bin/nova-manage user admin admin admin admin

A new project “admin” managed by “admin” is created:

$NOVA_DIR/bin/nova-manage project create admin admin

A small network is created with 32 IPs from the fixed range:

$NOVA_DIR/bin/nova-manage network create private $FIXED_RANGE 1 32

Create some floating IPs using the floating range.

$NOVA_DIR/bin/nova-manage floating create $FLOATING_RANGE

Download an image from ansolabs and untar it in the images dir.

if [ ! -d $DIR/images ]; then
   mkdir -p $DIR/images
   wget -c http://images.ansolabs.com/tty.tgz
   tar -C $DIR/images -zxf tty.tgz
fi

If ami-tty image in images service then convert the image in directory from the old (Bexar) format to the new format.

if ! glance details | grep ami-tty; then
    $NOVA_DIR/bin/nova-manage image convert $DIR/images
fi

The file novarc looks like this for me. I am running things on Amazon EC2 right now. 10.240.95.3 is my EC2 instance private IP. The EC2 API server is listening on port 8773 and the OpenStack API is listening on port 8774. The data store will be running on port 3333.

The different servers, controllers and stores are started and we can browse them through the screen session created above. See Nova Concepts and Introduction for more details on the Nova architecture.

screen_it api "$NOVA_DIR/bin/nova-api"
screen_it objectstore "$NOVA_DIR/bin/nova-objectstore"
screen_it compute "$NOVA_DIR/bin/nova-compute"
screen_it network "$NOVA_DIR/bin/nova-network"
screen_it scheduler "$NOVA_DIR/bin/nova-scheduler"
screen_it volume "$NOVA_DIR/bin/nova-volume"
screen_it ajax_console_proxy "$NOVA_DIR/bin/nova-ajax-console-proxy"
sleep 2
$NOVA_DIR/bin/nova-manage project zipfile admin admin $NOVA_DIR/nova.zip
unzip -o $NOVA_DIR/nova.zip -d $NOVA_DIR/
screen_it test "export PATH=$NOVA_DIR/bin:$PATH;. $NOVA_DIR/novarc"
if [ "$CMD" != "run_detached" ]; then
  screen -S nova -x
fi
NOVA_KEY_DIR=$(pushd $(dirname $BASH_SOURCE)>/dev/null; pwd; popd>/dev/null)
export EC2_ACCESS_KEY="admin:admin"
export EC2_SECRET_KEY="admin"
export EC2_URL="http://10.240.95.3:8773/services/Cloud"
export S3_URL="http://10.240.95.3:3333"
export EC2_USER_ID=42 # nova does not use user id, but bundling requires it
export EC2_PRIVATE_KEY=${NOVA_KEY_DIR}/pk.pem
export EC2_CERT=${NOVA_KEY_DIR}/cert.pem
export NOVA_CERT=${NOVA_KEY_DIR}/cacert.pem
export EUCALYPTUS_CERT=${NOVA_CERT} # euca-bundle-image seems to require this set
alias ec2-bundle-image="ec2-bundle-image --cert ${EC2_CERT} --privatekey ${EC2_PRIVATE_KEY} --user 42 --ec2cert ${NOVA_CERT}"
alias ec2-upload-bundle="ec2-upload-bundle -a ${EC2_ACCESS_KEY} -s ${EC2_SECRET_KEY} --url ${S3_URL} --ec2cert ${NOVA_CERT}"
export CLOUD_SERVERS_API_KEY="admin"
export CLOUD_SERVERS_USERNAME="admin"
export CLOUD_SERVERS_URL="http://10.240.95.3:8774/v1.0/"

Once the nova session is finished, the instances are terminated and the volumes deleted. This code is also run when the command “terminate” is used.

if [ "$CMD" == "run" ] || [ "$CMD" == "terminate" ]; then
    # shutdown instances
    . $NOVA_DIR/novarc; euca-describe-instances | grep i- | cut -f2 | xargs     euca-terminate-instances
    sleep 2
    # delete volumes
    . $NOVA_DIR/novarc; euca-describe-volumes | grep vol- | cut -f2 | xargs -n1 euca-delete-volume
    sleep 2
fi

The screen session is forced to shutdown. This part is also called when the command “clean” is executed.

if [ "$CMD" == "run" ] || [ "$CMD" == "clean" ]; then
    screen -S nova -X quit
    rm *.pid*
fi

There is one last command called “scrub” which is used to remove the bridges and VLANs configuration. It also destroys the domains.

if [ "$CMD" == "scrub" ]; then
    $NOVA_DIR/tools/clean-vlans
    if [ "$LIBVIRT_TYPE" == "uml" ]; then
        virsh -c uml:///system list | grep i- | awk '{print \$1}' | xargs -n1   virsh -c uml:///system destroy
    else
        virsh list | grep i- | awk '{print \$1}' | xargs -n1 virsh destroy
    fi
fi

When you run the branch, install and run commands, you end up with the different components (API server, object store, scheduler…) running which is really neat.

You can use the screen windows to check each component status. Do a “man screen” to learn how to navigate between the different windows if you are not familiar with the tool screen.

API server:

# /opt/novascript/trunk/bin/nova-api
(nova.root 2011.1-LOCALBRANCH:LOCALREVISION): AUDIT [N/A] Starting /opt/novascript/trunk/bin/nova-api on 0.0.0.0:8774
(nova.root 2011.1-LOCALBRANCH:LOCALREVISION): AUDIT [N/A] Starting /opt/novascript/trunk/bin/nova-api on 0.0.0.0:8773

Object store:

(nova.root 2011.1-LOCALBRANCH:LOCALREVISION): DEBUG [N/A] network_topic : network from MainProcess (pid=13763) serve /opt/novascript/trunk/nova/twistd.py:266
(nova.root 2011.1-LOCALBRANCH:LOCALREVISION): AUDIT [N/A] Starting nova-objectstore
2011-01-14 22:15:09+0000 [-] Log opened.
2011-01-14 22:15:09+0000 [-] twistd 10.0.0 (/usr/bin/python 2.6.5) starting up.
2011-01-14 22:15:09+0000 [-] reactor class: twisted.internet.selectreactor.SelectReactor.
2011-01-14 22:15:09+0000 [-] twisted.web.server.Site starting on 3333
2011-01-14 22:15:09+0000 [-] Starting factory <twisted.web.server.Site instance at 0xaaf5bec>

You can launch a new instance this way:

cd /tmp/
euca-add-keypair test > test.pem
euca-run-instances -k test -t m1.tiny ami-tty

You can list your instances this way:

# euca-describe-instances
RESERVATION     r-ky6gm38t      admin
INSTANCE        i-00000001      ami-tty        10.0.0.3        10.0.0.3        running test (admin, domU-12-31-39-04-58-F5)    0               m1.tiny 2011-01-14 22:32:03.466420      nova

Running ifconfig, we can now see the bridge and VLAN created

br100     Link encap:Ethernet  HWaddr 12:31:39:04:58:f5  
          inet addr:10.0.0.1  Bcast:10.0.0.31  Mask:255.255.255.224
...
vlan100   Link encap:Ethernet  HWaddr 12:31:39:04:58:f5  
          inet6 addr: fe80::1031:39ff:fe04:58f5/64 Scope:Link
...

A network route has been set up for us to use the bridge interface when the packets are sent to 10.0.0.x:

# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.0.0.0        0.0.0.0         255.255.255.224 U     0      0        0 br100
...

VoilĂ . Don’t hesitate to post a comment if you have any feedback.

Distributed messaging using RabbitMQ and Python

January 8, 2011

This tutorial shows how to distribute messages to multiple servers for processing. We will be using RabbitMQ which is based on AMQP.

The way messaging works is that you have producers producing messages and consumers consuming them. We are going to have 1 producer generating random integers and 2 consumers: 1 consuming the odd integers and 1 consuming the even integers.

The producer sends messages to the exchange and the consumers create and bind queues to the exchange to receive the messages they are interested in. The way messages are routed is using routing keys for the messages and binding keys for the queues.

The producer will be running on the server “producer.messaging.com”, 1 consumer will be running on “consumer1.messaging.com” and the other one on “consumer2.messaging.com”.

Requirements

We need to install RabbitMQ server on producer.messaging.com and py-amqplib on the 3 servers.

Here, I am using Ubuntu on all the servers.

sudo apt-get install rabbitmq-server
sudo apt-get install python-amqplib

After installing rabbitmq-server, the server should be up and running.

Producer

First thing to do is to create a Python class for the producer. The constructor will initiate a connection to the RabbitMQ server. The constructor also takes as argument the name of the exchange which will receive all the messages from the producer.

from amqplib import client_0_8 as amqp

class Producer(object):
    def __init__(self, exchange_name, host, userid, password):
        """
        Constructor. Initiate connection with the RabbitMQ server.

        @param exchange_name name of the exchange to send messages to
        @param host RabbitMQ server host 
        @param userid RabbitMQ server username
        @param password RabbitMQ server user's password
        """
        self.exchange_name = exchange_name
        self.connection = amqp.Connection(host=host, userid=userid,
            password=password, virtual_host="/", insist=False)
        self.channel = self.connection.channel()
    ...

We also need a method to publish a message to the exchange. This method takes the message and its routing key as arguments. The content type of the message here is text but you can change it. Also, we use persistence mode for the message (delivery_mode = 2) so best-effort is used for delivery.

    def publish(self, message, routing_key):
        """
        Publish message to exchange using routing key

        @param text message to publish
        @param routing_key message routing key
        """
        msg = amqp.Message(message)
        msg.properties["content_type"] = "text/plain"
        msg.properties["delivery_mode"] = 2
        self.channel.basic_publish(exchange=self.exchange_name,
                                   routing_key=routing_key, msg=msg)

At last, we add a method to close the channel and the connection.

    def close(self):
        """
        Close channel and connection
        """
        self.channel.close()
        self.connection.close()

We will come back to this class when we look at our example.

Consumer

Let’s create our Consumer class to help us create consumers.

from amqplib import client_0_8 as amqp

class Consumer(object):
    def __init__(self, host, userid, password):
        """
        Constructor. Initiate a connection to the RabbitMQ server.

        @param host RabbitMQ server host 
        @param userid RabbitMQ server username
        @param password RabbitMQ server user's password
        """
        self.connection = amqp.Connection(host=host, userid=userid,
            password=password, virtual_host="/", insist=False)
        self.channel = self.connection.channel()

Our close method:

    def close(self):
        """
        Close channel and connection
        """
        self.channel.close()
        self.connection.close()

Next is a method to create an exchange. We use the exchange type “direct” which means the broker routes the message using a straight match on the routing key. If a we bind a queue using the key ‘test’, only the messages with the routing key ‘test’ will be sent to it.

    def declare_exchange(self, exchange_name, durable=True, auto_delete=False):
        """
        Create exchange.

        @param exchange_name name of the exchange
        @param durable will the server survive a server restart
        @param auto_delete should the server delete the exchange when it is
        no longer in use
        """
        self.exchange_name = exchange_name
        self.channel.exchange_declare(exchange=self.exchange_name,
            type='direct', durable=durable, auto_delete=auto_delete)

Followed by a method to create a queue and bind it to the exchange. We need to indicate the binding key so the exchange knows which messages to send to the queue based on their routing key.

    def declare_queue(self, queue_name, routing_key, durable=True, exclusive=False, auto_delete=False):
        """
        Create a queue and bind it to the exchange.

        @param queue_name Name of the queue to create
        @param routing_key binding key
        @param durable will the queue service a server restart
        @param exclusive only 1 client can work with it
        @param auto_delete should the server delete the exchange when it is 
                no longer in use
        """
        self.queue_name = queue_name
        self.routing_key = routing_key
        self.channel.queue_declare(queue=self.queue_name, durable=durable,
                                   exclusive=exclusive, auto_delete=auto_delete)
        self.channel.queue_bind(queue=self.queue_name,
            exchange=self.exchange_name, routing_key=self.routing_key)

At this point, we can get define a method to start a consumer and use a callback for each message delivered. The next method allows us to do that.

    def start_consuming(self, callback, queue_name=None, consumer_tag='consumer'):
        """
        Start a consumer and register a function to be called when a message is consumed

        @param callback function to call
        @param queue_name name of the queue
        @param consumer_tag a client-generated consumer tag to establish context
        """
        if hasattr(self, 'queue_name') or queue_name:
            self.channel.basic_consume(queue=getattr(self, 'queue_name',
                    queue_name),
                callback=callback, consumer_tag=consumer_tag)

We need the opposite of the previous method.

    def stop_consuming(self, consumer_tag='consumer'):
        """
        Cancel a consumer.

        @param consumer_tag a client-generated consumer tag to establish context
        """
        self.channel.basic_cancel(consumer_tag)

Last, we need a wait method to ask the consumer to be patient and wait for activity on the channel: ie: messages being delivered.

    def wait(self):
        """
        Wait for activity on the channel.
        """
        while True:
            self.channel.wait()

Example

Let’s put our classes into action. We are going to define a producer and 2 consumers as described at the beginning of this tutorial.

First, our producer will generate random integers and send them to the exchange. If the integer is odd, it will use the routing key “odd” and if the integer is even, it will use the routing key “even”. This code will be running on “producer.messaging.com”.

import random, time
import producer
p = producer.Producer(exchange_name='integers', host='producer.messaging.com', userid='guest', password='guest')
while True:
    # generate a random integer between 1 and 100 included
    i = random.randint(1, 100)
    if i % 2 == 0:
        key = 'even'
    else:
        key = 'odd'
    p.publish(str(i), key)
    print 'integer: %d' % i 
    time.sleep(1) 

Next is our first consumer running on “consumer1.messaging.com”. We declare an exchange named “integers” and create a queue and bind it to receive messages with the routing key “odd”. If the exchange and queue already exist, calling declare_exchange() and declare_queue() does nothing.

We define a callback function which prints the odd integer and acknowledge the message so the exchange knows it has been delivered properly.

import consumer

c = consumer.Consumer(host='producer.messaging.com', userid='guest', password='guest')
c.declare_exchange(exchange_name='integers')
c.declare_queue(queue_name='odds', routing_key='odd')

def message_callback(message):
    print 'odd integer: %s' % message.body
    c.channel.basic_ack(message.delivery_tag)

c.start_consuming(message_callback)
c.wait()

First time we call declare_exchange() and declare_queue(), we end up with the following:

Similar to the first consumer, we create another consumer on “consumer2.messaging.com” to consume the even integers.

import consumer

c = consumer.Consumer(host='producer.messaging.com', userid='guest', password='guest')
c.declare_exchange(exchange_name='integers')
c.declare_queue(queue_name='evens', routing_key='even')

def message_callback(message):
    print 'even integer: %s' % message.body
    c.channel.basic_ack(message.delivery_tag)

c.start_consuming(message_callback)
c.wait()

We end up with the following:

Let’s start our 2 consumers and our producer. This is the output of each:

Producer:

integer: 14
integer: 47
integer: 6
integer: 83
...

Consumer 1:

odd integer: 47
odd integer: 83

Consumer 2:

even integer: 14
even integer: 6 

On the server where rabbitmq is running, we can use rabbitmqctl to take a look at the objects created.

List the exchanges. We can see our exchange “integers” listed in the direct category.

$ sudo rabbitmqctl list_exchanges
Listing exchanges ...
amq.direct             direct
amq.topic              topic
amq.rabbitmq.logging   topic
amq.fanout             fanout
integers direct
amq.headers            headers
  direct
amq.match              headers
...done.

List the queues. We can check the number of messages to be consumed.

$ sudo rabbitmqctl list_queues
Listing queues ...
evens    4
odds     3
...done.

If you want to remove the exchange, all the queues. You have to first stop the app, then issue a reset follow by a start.

$ sudo rabbitmqctl stop_app
$ sudo rabbitmqctl reset
$ sudo rabbitmqctl start_app

I hope you enjoyed this tutorial. You can see that RabbitMQ is really easy to use and very powerful. Do not hesitate to add a comment if you have any feedback. If you need help with a project written in Python or with building a new web service, I am available as a freelancer: LinkedIn profile. Follow me on Twitter @laurentluce.

Add REST resources to a database using Python and SQLAchemy

December 26, 2010

This tutorial shows how to download REST resources to your database using Python and SQLAchemy. We are going to download manufacturers and cars resources to our database. We will also show how easy it is to query them once they are stored.

Requirements

We need to install SQLAchemy and simplejson Python libraries:

easy_install SQLAlchemy
easy_install simplejson

You can use any database supported by SQLAlchemy: List of databases. I am using MySQL.

Imports

We need to import the following modules for our code to work fine.

import urllib2
import simplejson
from sqlalchemy import Column, Integer, String, ForeignKey, create_engine
from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base

Tables, classes and mappers

SQLAchemy declarative configuration style allows us to define our tables, resources classes and the mapping between the class and the tables just by declaring 2 classes derived from the class Base.

Base = declarative_base()

Let’s define the manufacturers table, class and mapper.

class Manufacturer(Base):
  """
  Manufacturer resource
  """
  __tablename__ = 'manufacturers'
  
  id = Column(Integer, primary_key=True)
  name = Column(String(32), unique=True)
  country = Column(String(32))

  def __init__(self, resource):
    """
    Class instantiation

    @param resource resource JSON attribute
    """
    self.name = resource['name']
    self.country = resource['country']
    
  def __repr__(self):
    return "<manufacturer('%s in %s')>" % (self.name, self.country)

Let’s define the cars table, class and mapper. Note how the relationship between the car and its manufacturer is defined.

class Car(Base):
  """
  Car resource
  """
  __tablename__ = 'cars'
        
  id = Column(Integer, primary_key=True)
  name = Column(String(32), unique=True)
  max_speed = Column(Integer)
  year_released = Column(Integer)
  manufacturer_id = Column(Integer, ForeignKey('manufacturers.id'))

  manufacturer = relationship(Manufacturer, backref=backref('cars', order_by=id))

  def __init__(self, resource):
    """
    Class instantiation

    @param resource resource JSON attribute
    """
    self.name = resource['name']
    self.max_speed = resource['max_speed']
    self.year_released = resource['year_released']
    
  def __repr__(self):
    return "<car('%s - max speed: %d, released in %s')>" % (self.name, self.max_speed, self.year_released)

Our main class is named “Resources” and we will add methods to setup a connection with the database, create the tables and download the resources.

class Resources:
  """
  Main class 
  """
  def __init__(self):
    """
    Class instantiation

    @param settings dict of settings
    """
    # database engine
    self.engine = None
    # Collection of tables and their associated schema constructs
    self.metadata = None
    # database session
    self.session = None

    self.setup_connection()
    self.setup_tables()
    self.process()

  def setup_connection(self):
    """
    Create DB session
    """
    ...

  def setup_tables(self):
    """
    Create tables in database.
    """
    ...

  def process(self):
    """
    Get resources and add them to DB
    """
    ...

Let’s look at how we can setup a connection to our database. We call create_engine() and pass the database dialect and connection arguments. In our case, we use the string: ‘mysql://root:password@localhost:3306/vroom’ where vroom is the name of the database. This allows us to define our session using the Session factory. This session is the ORM’s handle to the database.

  def setup_connection(self):
    """
    Create DB session
    """
    s = 'mysql://root:password@localhost:3306/vroom'
    self.engine = create_engine(s)
    Session = sessionmaker(bind=self.engine)
    self.session = Session()

Next is the tables creation using our tables definition. create_all() can be called multiple times safely and only missing tables will be created.

  def setup_tables(self):
    """
    Create tables in database.
    """
    self.metadata = Base.metadata
    self.metadata.create_all(self.engine)

Using simplejson and urllib2, it is really easy to get resources and retrieve them as a dictionary. Let’s get the list of manufacturers.

  def process(self):
    ...
    data = simplejson.load(urllib2.urlopen("http://api_url/manufacturers"))

data will contain something like this:

{"manufacturers": [
  {"name": "Peugeot", "country": "France"},
  {"name": "Chevrolet", "country": "USA"},
  ]
}

For each manufacturer, we need to get the list of cars so we can add the resources to the database. SQLAchemy will take care of setting up the foreign keys values for us: ie: relations between the manufacturers and the cars.

    for i, m in enumerate(data['manufacturers']):
      # create Manufacturer object
      manufacturer = Manufacturer(m)
      # get cars for this manufacturer
      data2 = simplejson.load(urllib2.urlopen("http://api_url/manufacturers/%s" % manufacturer['name']))
      # fill-up list of Car objects
      cars = []
      for c in data2['cars'][i]:
        # create Car object and add it to cars list
        cars.append(Car(c))
      # add cars list to manufacturer's cars list
      manufacturer.cars = cars
      # add manufacturer object to DB
      self.session.add(manufacturer)
    # commit changes to DB
    self.session.commit()

Once the objects are in the database, it is easy to query them using the ORM. For example, let’s print the list of manufacturers along with their cars.

for m in self.session.query(Manufacturer).order_by(Manufacturer.id):
  print m
  for c in m.cars:
    print '-- %s' % c

The output should be something like this:

<manufacturer('Peugeot in France')>
-- <car('307 - max speed: 180, released in 2002')>
<manufacturer('Chevrolet in USA')>
-- <car('Malibu - max speed: 190, released in 2010')>

That’s it for now. Please add a comment if you have any feedback.

Python module to download Twilio REST resources to your database

December 21, 2010

Are you are looking for an easy way to download your Twilio resources (calls, sms, notifications, recordings…) to your own database so you can access them faster and also when you are offline? Twilio Resources DB is a Python module doing just that.

Features

  • Download Twilio resources to a database.
  • Download recordings audio files.
  • Support for MySQL, PostgreSQL and Redis.
  • Automatic database tables creation for SQL DB.
  • Handle tables relations for SQL DB.
  • Option to download new resources continuously.

This is the list of resource types we support:

  • Accounts
  • Calls
  • Notifications
  • Recordings
  • Transcriptions
  • Sms Messages
  • Conferences
  • Incoming Phone Numbers
  • Outgoing Caller Ids

Requirements

We need to install simplejson so we can use the JSON format for the HTTP requests results: ie: resources returned using JSON instead of XML.

sudo easy_install simplejson

Depending on the database used, we need to install SQLAlchemy for MySQL/PostgreSQL or redis for Redis.

easy_install SQLAlchemy
easy_install redis

We also need the Twilio Python library helper to help us with the HTTP requests to the Twilio server:

git clone https://github.com/twilio/twilio-python.git
python setup.py install

At last, we need to install the Twilio resources DB library:

git clone https://github.com/laurentluce/twilio-python-utils.git
python setup.py install

Download resources to a database

Here is an example of a simple Python script using the library to download all the resources from the Twilio server to a MySQL database.

A “Resources” object needs to be created with a list of settings:

from twilioresourcesdb import resources

# settings
settings = {}
# Twilio account
settings['account_sid'] = 'Twilio account SID'
settings['account_token'] = 'Twilio account token'
# DB
settings['database_type'] = 'mysql'
settings['database_user'] = 'username'
settings['database_password'] = 'password'
settings['database_host'] = 'hostname or ip address'
settings['database_name'] = 'database name'
# Resources options
settings['check_frequency'] = 300 # check for new resources every 5 minutes
# Download recordings audio files
settings['download_recordings'] = True
settings['recording_path'] = '/data/recordings/'
settings['recording_format'] = 'wav'

# Instantiate resources object.
# This will setup a connection with the DB and create the table if necessary.
r = resources.Resources(settings)

Here is the full list of options:

  • account_sid : Twilio account SID – ‘ACxxxxx’
  • account_token : Twilio account token – ‘xxxxx’
  • database_type : type of database – ‘mysql’, ‘postgresql’, ‘redis’
  • database_user : database username – ‘xxx’ (default: ‘root’, not required for Redis)
  • database_password : database user password – ‘xxxx’ (default: None)
  • database_host : database ip address or hostname – ‘xxxx’ (default: ‘localhost’)
  • database_port : database port number – xxxx (default: 3306 for MySQL, 5432 for PostgreSQL, 6379 for Redis)
  • database_name : database name – ‘xxxx’ (not required for Redis database)
  • check_frequency : frequency in seconds on how often to check for new resources – xx (default: 5)
  • page_size : number of resources to download at each request – xxxx (default: 50)
  • download_recordings : enable recordings downloads (audio files) – True/False (default: False)
    • recording_path : absolute path to store recordings audio files – ‘/xxx/xxx/xxx…’
    • recording_format : audio files format: ‘wav’, ‘mp3′

Next, we can download all the resources and then stop or we can start a thread downloading the resources continuously.

First option – blocking call – download all resources then stop:

r.process()

Second option – start a thread to download resources continuously:

r.start()

Here is some code you may want to re-use to stop the thread when the script is interrupted:

while True:
  try:
    time.sleep(1)
  except (KeyboardInterrupt, SystemExit):
    r.stop = True
    r.join()
    break

When an in-progress resource is received from the server, it is placed in a temporary list and checked against for completion regularly so we only save completed resources in the database. For example, a queued SMS message will be placed in this temporary list and only saved to the database when it is in a termination state: ie: completed, failed, dropped…

MySQL and PostgreSQL database tables

When you create the Resources object, the tables are automatically created for you (not if they already exist).

Here is the list of tables created automatically:

  • accounts
  • calls
  • notifications
  • recordings
  • transcriptions
  • sms_messages
  • conferences
  • incoming_phone_numbers
  • outgoing_caller_ids

And those are the relationships using foreign keys:

  • calls -> accounts
  • sms_messages -> accounts
  • conferences -> accounts
  • incoming_phone_numbers -> accounts
  • outgoing_caller_ids -> accounts
  • notifications -> accounts
  • notifications -> calls
  • recordings -> accounts
  • recordings -> calls
  • transcriptions -> accounts
  • transcriptions -> recordings

Redis keys and values

In case of Redis, we add the resources using keys and values.

The resource’s key is the resource SID and the value is the JSON resource object.

Call resource: CAxxxx
SMS message resource: SMxxxx

That’s it for now. Don’t hesitate to add comments if you have any feedback.

Binary Search Tree library in Python

December 18, 2010

This article is about a Python library I created to manage binary search trees. I will go over the following:

  • Node class
  • Insert method
  • Lookup method
  • Delete method
  • Print method
  • Comparing 2 trees
  • Generator returning the tree elements one by one

You can checkout the library code on GitHub: git clone https://laurentluce@github.com/laurentluce/python-algorithms.git. This folder contains more libraries but we are just going to focus on the Binary Tree one.

As a reminder, here is a binary search tree definition (Wikipedia).

A binary search tree (BST) or ordered binary tree is a node-based binary tree data structure which has the following properties:

  • The left subtree of a node contains only nodes with keys less than the node’s key.
  • The right subtree of a node contains only nodes with keys greater than the node’s key.
  • Both the left and right subtrees must also be binary search trees.

Here is an example of a binary search tree:

Node class

We need to represent a tree node. To do that, we create a new class named Node with 3 attributes:

  • Left node
  • Right node
  • Node’s data (same as key in the definition above.)
class Node:
    """
    Tree node: left and right child + data which can be any object
    """
    def __init__(self, data):
        """
        Node constructor

        @param data node data object
        """
        self.left = None
        self.right = None
        self.data = data

Let’s create a tree node containing the integer 8. You can pass any object for the data so it is flexible. When you create a node, both left and right node equal to None.

root = Node(8)

Note that we just created a tree with a single node.

Insert method

We need a method to help us populate our tree. This method takes the node’s data as an argument and inserts a new node in the tree.

class Node:
    ...
    def insert(self, data):
        """
        Insert new node with data

        @param data node data object to insert
        """
        if data < self.data:
            if self.left is None:
                self.left = Node(data)
            else:
                self.left.insert(data)
        else:
            if self.right is None:
                self.right = Node(data)
            else:
                self.right.insert(data)

insert() is called recursively as we are locating the place where to add the new node.

Let’s add 3 nodes to our root node which we created above and let’s look at what the code does.

root.insert(3)
root.insert(10)
root.insert(1)

This is what happens when we add the second node (3):

  • 1- root node’s method insert() is called with data = 3.
  • 2- 3 is less than 8 and left child is None so we attach the new node to it.

This is what happens when we add the third node (10):

  • 1- root node’s method insert() is called with data = 10.
  • 2- 10 is greater than 8 and right child is None so we attach the new node to it.

This is what happens when we add the fourth node (1):

  • 1- root node’s method insert() is called with data = 1.
  • 2- 1 is less than 8 so the root’s left child (3) insert() method is called with data = 1. Note how we call the method on a subtree.
  • 3- 1 is less than 3 and left child is None so we attach the new node to it.

This is how the tree looks like now:

Let’s continue and complete our tree so we can move on to the next section which is about looking up nodes in the tree.

root.insert(6)
root.insert(4)
root.insert(7)
root.insert(14)
root.insert(13)

The complete tree looks like this:

Lookup method

We need a way to look for a specific node in the tree. We add a new method named lookup which takes a node’s data as an argument and returns the node if found or None if not. We also return the node’s parent for convenience.

class Node:
    ...
    def lookup(self, data, parent=None):
        """
        Lookup node containing data

        @param data node data object to look up
        @param parent node's parent
        @returns node and node's parent if found or None, None
        """
        if data < self.data:
            if self.left is None:
                return None, None
            return self.left.lookup(data, self)
        elif data > self.data:
            if self.right is None:
                return None, None
            return self.right.lookup(data, self)
        else:
            return self, parent

Let’s look up the node containing 6.

node, parent = root.lookup(6)

This is what happens when lookup() is called:

  • 1- lookup() is called with data = 6, and default value parent = None.
  • 2- data = 6 is less than root’s data which is 8.
  • 3- root’s left child lookup() method is called with data = 6, parent = current node. Notice how we call lookup() on a subtree.
  • 4- data = 6 is greater than node’s data which is now 3.
  • 5- node’s right child lookup() method is called with data = 6 and parent = current node
  • 6- node’s data is equal to 6 so we return it and its parent which is node 3.

Delete method

The method delete() takes the data of the node to remove as an argument.

class Node:
    ...
    def delete(self, data):
        """
        Delete node containing data

        @param data node's content to delete
        """
        # get node containing data
        node, parent = self.lookup(data)
        if node is not None:
            children_count = node.children_count()
        ...

There are 3 possibilities to handle:

  • 1- The node to remove has no child.
  • 2- The node to remove has 1 child.
  • 3- The node to remove has 2 children.

Let’s tackle the first possibility which is the easiest. We look for the node to remove and we set its parent’s left or right child to None.

    def delete(self, data):
        ...
        if children_count == 0:
            # if node has no children, just remove it
            if parent.left is node:
                parent.left = None
            else:
                parent.right = None
            del node
        ...

Note: children_count() returns the number of children of a node.

Here is the function children_count:

class Node:
    ...
    def children_count(self):
        """
        Returns the number of children

        @returns number of children: 0, 1, 2
        """
        if node is None:
            return None
        cnt = 0
        if self.left:
            cnt += 1
        if self.right:
            cnt += 1
        return cnt

For example, we want to remove node 1. Node 3 left child will be set to None.

root.delete(1)

Let’s look at the second possibility which is the node to be removed has 1 child. We replace the node’s data by its left or right child’s data and we set its left or right child to None.

    def delete(self, data):
        ...
        elif children_count == 1:
            # if node has 1 child
            # replace node by its child
            if node.left:
                    n = node.left
            else:
                    n = node.right
            if parent:
                if parent.left is node:
                        parent.left = n
                else:
                        parent.right = n
            del node
        ...

For example, we want to remove node 14. Node 14 data will be set to 13 (its left child’s data) and its left child will be set to None.

root.delete(14)

Let’s look at the last possibility which is the node to be removed has 2 children. We replace its data with its successor’s data and we fix the successor’s parent’s child.

    def delete(self, data):
        ...
        else:
            # if node has 2 children
            # find its successor
            parent = node
            successor = node.right
            while successor.left:
                parent = successor
                successor = successor.left
            # replace node data by its successor data
            node.data = successor.data
            # fix successor's parent's child
            if parent.left == successor:
                parent.left = successor.right
            else:
                parent.right = successor.right

For example, we want to remove node 3. We look for its successor by going right then left until we reach a leaf. Its successor is node 4. We replace 3 with 4. Node 4 doesn’t have a child so we set node 6 left child to None.

root.delete(3)

Print method

We add a method to print the tree inorder. This method has no argument. We use recursion inside print_tree() to walk the tree breath-first. We first traverse the left subtree, then we print the root node then we traverse the right subtree.

class Node:
    ...
    def print_tree(self):
        """
        Print tree content inorder
        """
        if self.left:
            self.left.print_tree()
        print self.data,
        if self.right:
            self.right.print_tree()

Let’s print our tree:

root.print_tree()

The output will be: 1, 3, 4, 6, 7, 8, 10, 13, 14

Comparing 2 trees

To compare 2 trees, we add a method which compares each subtree recursively. It returns False when one leaf is not the same in both trees. This includes 1 leaf missing in the other tree or the data is different. We need to pass the root of the tree to compare to as an argument.

class Node:
    ...
    def compare_trees(self, node):
        """
        Compare 2 trees

        @param node tree's root node to compare to
        @returns True if the tree passed is identical to this tree
        """
        if node is None:
            return False
        if self.data != node.data:
            return False
        res = True
        if self.left is None:
            if node.left:
                return False
        else:
            res = self.left.compare_trees(node.left)
        if self.right is None:
            if node.right:
                return False
        else:
            res = self.right.compare_trees(node.right)
        return res

For example, we want to compare tree (3, 8, 10) with tree (3, 8, 11)

# root2 is the root of tree 2
root.compare_trees(root2)

This is what happens in the code when we call compare_trees().

  • 1- The root node compare_trees() method is called with the tree to compare root node.
  • 2- The root node has a left child so we call the left child compare_trees() method.
  • 3- The left subtree comparison will return True.
  • 2- The root node has a right child so we call the right child compare_trees() method.
  • 3- The right subtree comparison will return False because the data is different.
  • 4- compare_trees() will return False.

Generator returning the tree elements one by one

It is sometimes useful to create a generator which returns the tree nodes values one by one. It is memory efficient as it doesn’t have to build the full list of nodes right away. Each time we call this method, it returns the next node value.

To do that, we use the yield keyword which returns an object and stops right there so the function will continue from there next time the method is called.

We cannot use recursion in this case so we use a stack.

Here is the code:

class Node:
    ...
    def tree_data(self):
        """
        Generator to get the tree nodes data
        """
        # we use a stack to traverse the tree in a non-recursive way
        stack = []
        node = self
        while stack or node: 
            if node:
                stack.append(node)
                node = node.left
            else: # we are returning so we pop the node and we yield it
                node = stack.pop()
                yield node.data
                node = node.right

For example, we want to access the tree nodes using a for loop:

for data in root.tree_data():
    print data

Let’s look at what happens in the code with the same example we have been using:

  • 1- The root node tree_data() method is called.
  • 2- Node 8 is added to the stack. We go to the left child of 8.
  • 3- Node 3 is added to the stack. We go to the left child of 3.
  • 4- Node 1 is added to the stack. Node is set to None because there is no left child.
  • 5- We pop a node which is Node 1. We yield it (returns 1 and stops here until tree_data() is called again.
  • 6- tree_data() is called again because we are using it in a for loop.
  • 7- Node is set to None because Node 1 doesn’t have a right child.
  • 8- We pop a node which is Node 3. We yield it (returns 3 and stops here until tree_data() is called again.

Here you go, I hope you enjoyed this tutorial. Don’t hesitate to add comments if you have any feedback.

C++ Twilio REST and TwiML Helper Library

December 16, 2010

The C++ Twilio help library simplifies the process of making requests to the Twilio REST API, generating TwiML and validating HTTP request signatures using C++.

Checkout the library: git clone https://laurentluce@github.com/laurentluce/twilio-cplusplus.git

Source files:

  • Rest.cpp: Twilio REST helper
  • TwiML.cpp: TwiML generation helper
  • Utils.cpp: Utils to validate a HTTP request signature: X-Twilio-Signature
  • Examples.cpp: Examples of usage
  • UnitTests.h: Unit tests
  • Makefile: makefile to build the library (requires libcurl and openssl), examples code and unit tests suite. Type ‘make’ to build the library. ‘make examples’ to build the examples, ‘make unittests’ to build the unit tests suite and run it.

Don’t forget to include Utils.h, Rest.h and TwiML.h at the top of your source code and to use the twilio namespace: “using namespace twilio;”.

The following code examples are based on a restaurant dish recommendation service using SMS for communication. We will go through the following examples:

1- Receiving and replying to a SMS asking for a restaurant dish recommendation.
2- Sending a daily recommendation to a list of subscribers using SMS.
3- Retrieving the list of SMS we sent.

We are going to use the C++ std namespace in the rest of the code: “using namespace std;”.

We need to define the following constants:

// Twilio REST API version
const string API_VERSION = "2010-04-01";
// Twilio Account Sid
const string ACCOUNT_SID = "XXXX";
// Twilio Auth Token
const string ACCOUNT_TOKEN = "XXXX";
// Twilio SMS URL
const string SMS_URL = "http://www.example.com/sms";

Receiving and replying to a SMS asking for a restaurant dish recommendation.

When someone asks for a dish recommendation, he sends a SMS with a text such as “Burger in San Francisco”. Twilio issues a HTTP POST request to your server with the text in the POST parameter “Body”. The URL called is SMS_URL.

First, we need to validate the HTTP POST request to make sure it is coming from a Twilio server. We use the Utils class to help us with that.

Utils utils (ACCOUNT_SID, ACCOUNT_TOKEN);
// vars should contain the POST parameters received. It is a vector of Var structures: key/value.
vector<Var> vars;
vars.push_back(Var("AccountSid", "xxxx"));
vars.push_back(Var("Body", "xxxx"));
// ... add all POST parameters received
// signature is the hash we received in the X-Twilio-Signature header
bool valid = utils.validateRequest(signature, SMS_URL, vars);
if(!valid)
  // ... handle invalid request here

Then we need to reply with our dish recommendation. We use the classes TwiMLResponse and Sms to help us generate the XML.

TwiMLResponse response;
// Set SMS text based on what the user asked, which can be found in the "Body" POST parameter.
Sms sms ("Gigantic Burger at Twilio at 501 Folsom St San Francisco");
response.append(sms);
// ... reply with response.toXML()

response.toXML() returns the following:

<Response>
  <Sms>
    <![CDATA[Gigantic Burger at Twilio at 501 Folsom St San Francisco]]>
  </Sms>
</Response>

Sending a daily recommendation to a list of subscribers using SMS.

We need to issue POST requests using the following URL: /2010-04-01/Accounts/{AccountSid}/SMS/Messages. In the following code, “subscribers” is a vector containing the phone numbers that we need to send to. We use the class Rest to help us send the SMS messages.

Rest rest(ACCOUNT_SID, ACCOUNT_TOKEN);
// Fill up the POST parameters vector
vector<Var> vars;
vars.push_back(Var("From", "xxx-xxx-xxxx"));
vars.push_back(Var("Body", "Deluxe Ramen at Twilio at 501 Folsom St San Francisco"));
vars.push_back(Var("To", ""));
string res;
// Go through the list of subscribers and issue a POST request for each one. 
for(unsigned int i = 0; i < subscribers.size(); i++)
{
  // vars[2] refers to the "To" parameter
  vars[2].value = subscribers[i];
  res = rest.request("/" + API_VERSION + "/Accounts/" + ACCOUNT_SID + "/SMS/Messages", "POST", vars);
  // "res" contains the XML response returned by the Twilio server
}

Retrieving the list of SMS messages we sent and their status

We need to issue a GET request using the following URL: /2010-04-01/Accounts/{AccountSid}/SMS/Messages. We use the class Rest to help us with the GET request.

Rest rest(ACCOUNT_SID, ACCOUNT_TOKEN);
// Fill up the GET parameters vector
vector<Var> vars;
vars.push_back(Var("From", "xxx-xxx-xxxx"));
// we can also set "To" and "DateSent" to filter more
string res;
res = rest.request("/" + API_VERSION + "/Accounts/" + ACCOUNT_SID + "/SMS/Messages", "GET", vars);

“res” should contain the list of SMS sent.

<TwilioResponse>
  <SMSMessages page="0" numpages="6"...>
    <SMSMessage>
      <Sid>SM800f449d0399ed014aae2bcc0cc2f2ec</Sid>
      <DateCreated>Mon, 10 Dec 2010 03:45:01 +0000</DateCreated>
      ...
    </SMSMessage>
    ...
  </SMSMessages>
</TwilioResponse>

Note that pagination might need to be handled if the number of SMS messages is too large for one HTTP response. See http://www.twilio.com/docs/api/2010-04-01/rest/response#response-formats-list-paging-information for more details.

There is a lot more you can do using the Twilio C++ library so go ahead and have fun with it.

Amazon S3 upload and download using Python/Django

October 7, 2010

This article describes how you can upload files to Amazon S3 using Python/Django and how you can download files from S3 to your local machine using Python.

We assume that we have a file in /var/www/data/ which we received from the user (POST from a form for example).

You need to create a bucket on Amazon S3 to contain your files. This can be done using the Amazon console.

Now, we are going to use the python library boto to facilitate our work.

We define the following variables in settings.py of our Django project:

BUCKET_NAME = 'bucket_name'
AWS_ACCESS_KEY_ID = ...
AWS_SECRET_ACCESS_KEY = ...

Upload to S3

Here is the code we use to upload the picture files:

def push_picture_to_s3(id):
  try:
    import boto
    from boto.s3.key import Key
    # set boto lib debug to critical
    logging.getLogger('boto').setLevel(logging.CRITICAL)
    bucket_name = settings.BUCKET_NAME
    # connect to the bucket
    conn = boto.connect_s3(settings.AWS_ACCESS_KEY_ID,
                    settings.AWS_SECRET_ACCESS_KEY)
    bucket = conn.get_bucket(bucket_name)
    # go through each version of the file
    key = '%s.png' % id
    fn = '/var/www/data/%s.png' % id
    # create a key to keep track of our file in the storage 
    k = Key(bucket)
    k.key = key
    k.set_contents_from_filename(fn)
    # we need to make it public so it can be accessed publicly
    # using a URL like http://s3.amazonaws.com/bucket_name/key
    k.make_public()
    # remove the file from the web server
    os.remove(fn)
  except:
    ...

Download from S3

As you saw, you can access the file using the URL: http://s3.amazonaws.com/bucket_name/key but you can also use the boto library to download the files. I do that to create a daily backup of the bucket’s files on my local machine.

Here is the script to do that:

import boto
import sys, os
from boto.s3.key import Key

LOCAL_PATH = '/backup/s3/'
AWS_ACCESS_KEY_ID = '...'
AWS_SECRET_ACCESS_KEY = '...'

bucket_name = 'bucket_name'
# connect to the bucket
conn = boto.connect_s3(AWS_ACCESS_KEY_ID,
                AWS_SECRET_ACCESS_KEY)
bucket = conn.get_bucket(bucket_name)
# go through the list of files
bucket_list = bucket.list()
for l in bucket_list:
  keyString = str(l.key)
  # check if file exists locally, if not: download it
  if not os.path.exists(LOCAL_PATH+keyString):
    l.get_contents_to_filename(LOCAL_PATH+keyString)

Upload to Django with progress bar using Ajax and jQuery

September 30, 2010

In this article, I am going to describe how I implemented upload to Django + progress bar with Ajax and jQuery. I needed this feature so users could post their dish pictures on Gourmious and follow the upload’s progress.

Client Side

We need a form so the user can select a file to upload.

<form id="form_upload" action="/upload" method="POST">
  <input type="file" name="picture" id="picture" />
  <input type="hidden" id="X-Progress-ID" name="X-Progress-ID" value=""/>
  <input type="hidden" id="id" name="id" value=""/>
  <input id="form_submit_button" class="tp-button" type="submit" value="Submit" />
  </form>

We added 2 hidden inputs, first one is ‘X-Progress-ID’ which is the upload ID so we can support simultaneous uploads on the server side. We will see later how that value is handled by the server.

We also have the hidden input ‘id’ representing the dish ID in our case.

We want to use Ajax to send the POST request so it can be integrated nicely in a modern web interface along with a progress bar. To support that, we are going to use the jQuery Form plugin.

The function ajaxSubmit() is going to take care of everything for us.

We generate a random string for this upload ID and set the input value to that string.
We need to specify a URL to be called for the upload and 2 callback functions: one to be called before the request and 1 after.

$('#X-Progress-ID').val('random string');
var options = {
  dataType: 'xml',
  url: '/upload?X-Progress-ID='+$('#X-Progress-ID').val(),
  beforeSubmit: showRequest,
  success: showResponse
}
$('#form_upload').ajaxSubmit(options);

The showRequest callback can be as simple as:

function showRequest(formData, jqForm, options) {
    // do something with formData
    return True;
}

In the showResponse function, you need to process the response and act on it. In my case, I process some xml returned by the server with a status value.

function showResponse(response) {
    // do something with response
}

When the user presses submit, we want to display a progress bar so we use the following JS code to add a progress bar to the form. The progressBar() method is part of the jQuery progress bar plugin.

$('#form_upload').find('#form_submit_input').append('&lt;span id="uploadprogressbar"&gt;&lt;/span&lt;');
$('#form_upload').find('#uploadprogressbar').progressBar();

Now, we need to add a function running every few seconds to get the upload progress from the server and update the progress bar accordingly.

To do that, we use setInterval() to issue a GET request to the server to get the progress value using the JSON format. We pass the upload ID to the server. When the value null is returned, we know that the upload is finished.

function startProgressBarUpdate(upload_id) {
  $("#uploadprogressbar").fadeIn();
  if(g_progress_intv != 0)
    clearInterval(g_progress_intv);
  g_progress_intv = setInterval(function() {
    $.getJSON("/get_upload_progress?X-Progress-ID="
+ upload_id, function(data) {
      if (data == null) {
        $("#uploadprogressbar").progressBar(100);
        clearInterval(g_progress_intv);
        g_progress_intv = 0;
        return;
      }
      var percentage = Math.floor(100 * parseInt(data.uploaded) / parseInt(data.length));
      $("#uploadprogressbar").progressBar(percentage);
    });
  }, 5000);
}

Server side

First, we need a function in views.py to handle the upload. This function handles the request: “/upload?X-Progress-ID=xxxx”. We are reading the file chunk by chunk to not use too much RAM. In my case, I return some xml containing the status value: upload OK or not.

def upload(request):
  id = request.POST['id']
  path = '/var/www/pictures/%s' % id
  f = request.FILES['picture']
  destination = open(path, 'wb+')
  for chunk in f.chunks():
    destination.write(chunk)
  destination.close()
  # return status to client
  ...

How do we keep track of the upload progress? We need to use a different file upload handler. We are going to use UploadProgressCachedHandler. We just need the class from this snippet, not the view function which we are going to write ourself. You can add the class to a file named uploadprogresscachedhandler in your project.

This handler saves the upload progress to the cache so it can be retrieved easily when we receive the requests from the client.

To enable this handler, we need to add the following to settings.py:

from django.conf import global_settings
FILE_UPLOAD_HANDLERS = ('uploadprogresscachedhandler.UploadProgressCachedHandler', ) \
+ global_settings.FILE_UPLOAD_HANDLERS

We also need to enable the cache system. We are going to use memcached. This goes inside settings.py too.

CACHE_BACKEND = 'memcached://127.0.0.1:11211/'

You need to make sure memcached and the python bindings are installed on your server.

We need to add a function in views.py to return the upload progress asked by the client every few seconds during the upload. This function handles the request “/get_upload_progress?X-Progress-ID=xxxx”. The progress value is stored using the key “remoteaddress_uploadid”.

from django.utils import simplejson
from django.core.cache import cache
def get_upload_progress(request):
  cache_key = "%s_%s" % (request.META['REMOTE_ADDR'], request.GET['X-Progress-ID'])
  data = cache.get(cache_key)
  return HttpResponse(simplejson.dumps(data))

That’s it for now. Don’t hesitate to add comments to discuss more.

If you like this article, it would be cool if you can share your favorite restaurant dishes on Gourmious.

Facebook oauth and Graph API with Django

August 23, 2010

In this post, I am going to describe how I integrated Facebook into Gourmious, so users could post their favorite dishes on Gourmious and on Facebook at the same time.

I wanted to keep the Gourmious login so the users could decide to login using their Gourmious credentials or using the Facebook login feature. I also wanted all users to have a Gourmious account so I didn’t allow users to login using their Facebook credentials if they didn’t have a Gourmious account. I decided to support the following 3 scenarios :

- user has a Django account and he logs in with Facebook. I associate his Facebook account to his Django account. This needs to be done only once.
- user does not have a Django account and tries to login using Facebook. I ask him first to create a Django account and I associate both accounts.
- user logs in using his Django credentials.

Facebook oauth is easier than the old Facebook connect. I was so happy to migrate to this new scheme.

I assume that you already have a server running your Django application and a Facebook app.

We are going to use the Django app django_facebook_oauth to make our life easier. Make sure you have python simplejson package installed.

Facebook Platform uses the OAuth 2.0 protocol for authentication and authorization. Read this first before continuing.
Facebook authentication

Clone the Django Facebook oauth app source code:

git clone http://github.com/dickeytk/django_facebook_oauth.git

Copy the folder django_facebook_oauth to your Django project apps folder and rename it to facebook.

We will assume that your apps folder is called apps.

Django settings.py

We need to make the following changes:

  • Add ‘apps.facebook.backend.FacebookBackend’ to AUTHENTICATION_BACKENDS.
  • Add ‘apps.facebook’ to INSTALLED_APPS
  • Add your Facebook API app ID: API_ID = xxxx.
  • Add your Facebook secret key: APP_SECRET = xxxx.

Django urls.py

Add the following to urlpatterns:

(r'', include('apps.facebook.urls')),

Database changes

You need to add the following table so you can associate Django user IDs with Facebook user IDs and store the Facebook session token. You can use the syncdb feature or create the table manually.

CREATE TABLE `facebook_facebookuser` (
  `id` int(11) NOT NULL auto_increment,
  `user_id` int(11) NOT NULL,
  `facebook_id` varchar(150) NOT NULL,
  `access_token` varchar(150) default NULL,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `user_id` (`user_id`),
  UNIQUE KEY `facebook_id` (`facebook_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

Facebook authentication flow

1- user clicks on the Facebook login button
2- Django Facebook app authenticate view is called
3- facebook open graph authorize url is called with application ID, redirect uri = /authenticate, scope = ‘publish_stream’ to ask for permission to post to user’s wall
4- Dajgno Facebook app authenticate view is called with a parameter called code.
5- authentication using the Django Facebook app backend by passing the code back to Facebook along with the app secret.
6- Facebook returns a session token to be used for actions like posting messages to the user’s wall.
7- if the user is already in facebook_facebookuser table, login and redirect to home page. If user is not in the table, ask him to login using your app credentials so you can associate his Django app user id with his Facebook user id.

Django Facebook app views.py

I modified views.py to use my login form to associate Django user id with Facebook ID instead of using the register form included in the Django Facebook app.

When the token is returned and the user does not exist in the facebook table, I add a parameter to the request session to indicate that the user needs to login first so I can join the accounts.

if user != None:
  login(request, user)
  return HttpResponseRedirect('/')
else:
  # lines added
  request.session['ask_to_login_facebook'] = '1'
  return HttpResponseRedirect('/')
  #return HttpResponseRedirect('/register/')

I check for this session parameter in my template to popup a login form.

When the user enters his Django app credentials, I add an entry to the Facebook table before logging in the user. This operation just needs to be done once. After that, I know how to match a user Facebook id to the Django app user id so the user can directly login using the Facebook connect button.

This the code in my Django app views.py

# check credentials
user = authenticate(username=username, password=password)
if user is not None:
  if user.is_active:
      if 'fb_id' in request.session:
        fb_user = FacebookUser(user = user,
          facebook_id = request.session['fb_id'],
          access_token = request.session['fb_token'])
        fb_user.save()
        del request.session['fb_id']
        del request.session['fb_token']
      login(request, user)
      status = 0
    else:
      status = 1
  else:
    status = 1
    msg = _('Invalid password')

Django Facebook app backend.py

I modified this file to also add the token to the request session so I can use it when I add an entry to the facebook user table in views.py.

try:
  fb_user = FacebookUser.objects.get(facebook_id = str(profile["id"]))
except FacebookUser.DoesNotExist:
  request.session['fb_id'] = profile['id']
  # line added
  request.session['fb_token'] = access_token
  return None
fb_user.access_token = access_token
fb_user.save()

Django app template

This is what I added to my Django app login form to support the Facebook login button.

<form id="form_authenticate" action="/authenticate" method="POST">
  <p>blablabla</a></p>
  <input id="form_authenticate_button" type="image" src="link_to_facebook_button"                    onClick="javascript:$('#form_authenticate').submit();">
</form>

I also added a ‘Post to Facebook’ check box to my ‘Post dish review’ form so the user can decide what gets posted to his Facebook wall.

Post a message on the user’s wall

I used to post messages using the Javascript SDK but I now do it on the server side using the Facebook python SDK.

Add the file facebook.py (http://github.com/facebook/python-sdk/blob/master/src/facebook.py) to the Facebook app folder and rename it facebook_sdk.py .

We call put_wall_post() to post a message to the user’s wall.

Here is the code I am using in my Django app views.py to format the parameters before calling put_wall_post() .

def post_to_facebook(request):
  try:
    fb_user = FacebookUser.objects.get(user = request.user)
    # GraphAPI is the main class from facebook_sdp.py
    graph = GraphAPI(fb_user.access_token)
    attachment = {}
    message = 'test message'
    caption = 'test caption'
    attachment['caption'] = caption
    attachment['name'] = 'test name'
    attachment['link'] = 'link_to_picture'
    attachment['description'] = 'test description'
    graph.put_wall_post(message, attachment)
    return 0
  except:
    logging.debug('Facebook post failed')

That’s it for now.

If you enjoyed this article, check out my web app Gourmious to discover and share your favorite restaurant dishes. It would be cool if you could add some of your favorite restaurant dishes.

 
Powered by Wordpress and MySQL. Theme by Shlomi Noach, openark.org