Fabric Shell

I recently wrote a quick little fabric function to create a fabric shell. This works with a stock fabric install, you just might have to change where you import it from. It is at my Github page below.

https://github.com/mzupan/fabric_shell

If you place shell.py in the same directory as fabfile.py you can use it like

from fabric.api import *
from fabric.decorators import hosts

import shell

env.hosts = [
    'host1.domain.com',
    'host2.domain.com'
]

@hosts('')
def cmd():
    shell.shell()

Then you can use like like

$ fab cmd
[] Executing task 'cmd'
fabric::> id
[host1.domain.com] run: id
[host1.domain.com] out: uid=1020(mzupan) gid=1020(mzupan) groups=1020(mzupan)
[host1.domain.com] out: 
[host2.domain.com] run: id
[host2.domain.com] out: uid=1020(mzupan) gid=1020(mzupan) groups=1020(mzupan)
[host2.domain.com] out: 
fabric::> .sudo id
[host1.domain.com] sudo:  id
[host1.domain.com] out: uid=0(root) gid=0(root) groups=0(root)
[host1.domain.com] out: 
[host2.domain.com] sudo:  id
[host2.domain.com] out: uid=0(root) gid=0(root) groups=0(root)
[host2.domain.com] out: 
fabric::> 

Right now any command that is issued in the fabric shell gets run on all hosts in env.hosts and if you add .sudo in front of the command it will run them as root.

Fun with Django ManyToMany Fields

This is just another reason I find Django so easy.

For example.. Say you are making a monitoring application. Now you have a bunch of servers and you have certain groups of servers. A server can belong in more then one group also. So that is pretty easy and all with Django.

Now in your template you have a few pages. One page that displays the group information like all the servers in the group and another that displays the server information and will list all the groups that server is in. So below is my basic model layout

class Group(models.Model):
    parent = models.ForeignKey("Group", null=True, blank=True)
    user = models.ForeignKey(User)

    name = models.CharField(max_length=100)
    about = models.TextField(blank=True, null=True)

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return self.name

class Server(models.Model):
    hostname = models.CharField(max_length=300, db_index=True)
    about = models.TextField(blank=True, null=True)

    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    groups = models.ManyToManyField(Group)

    def __unicode__(self):
        return self.hostname

So to get all the groups my server is a member of is pretty easy.

s = Server.objects.get(id=1)
s.groups.all()

Now to get all the servers in a group is also easy

g = Group.objects.get(id=1)
g.server_set.all()

Get Django-NonRel working with VirtualEnv

MongoDB is getting a lot of press recently. I stumbled across a pretty interesting project called Django-nonrel. This is an attempt to build non-relational databases right into mongodb. Before you could always use a 3rd party ORM or just use pymongo right in Django but then you were missing out on all the nice apps the community has created. So the goal of this project is to make it so you can keep on using those. Now as of writing this joins are not working but from what I know they are working on it.

So lets start off by creating a virtualenv environment. I will call my project photoblog

virtualenv photoblog

Now lets activate our virtualenv

source ./photoblog/bin/activate

Now we can install Django-nonrel

wget http://bitbucket.org/wkornewald/django-nonrel/get/tip.zip
unzip tip.zip
cd django-nonrel
python setup.py build
python setup.py install

Now lets install pymongo

pip install pymongo

Now lets install the djangotoolbox

wget http://bitbucket.org/wkornewald/djangotoolbox/get/tip.zip
unzip tip.zip
cd djangotoolbox
python setup.py build
python setup.py install

Now we have to download and install the django-mongo-engine

wget --no-check-certificate https://github.com/aparo/django-mongodb-engine/zipball/master 
unzip aparo-django-mongodb-engine*
cd aparo-django-mongodb-engine*
python setup.py build
python setup.py install

So now lets create the Django project

cd ~/photoblog
django-admin.py startproject photoblog
cd photoblog

Now the only thing you really have to change to get a basic site up and running is the following file

settings.py

So edit that and find the block that looks like

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': '',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

And you want it to look something like

DATABASES = {
    'default': {
        'ENGINE': 'django_mongodb_engine.mongodb', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'photoblog',                      # Or path to database file if using sqlite3.
        'USER': '',                      # Not used with sqlite3.
        'PASSWORD': '',                  # Not used with sqlite3.
        'HOST': 'localhost',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '27017',                      # Set to empty string for default. Not used with sqlite3.
    }
}

Then you want to add the following to your INSTALLED_APPS

djangotoolbox

Now you can sync the db to set it all up!

cd ~/photoblog/photoblog
chmod +x manage.py
./manage.py syncdb

You are all set!

Tether your android phone to Fedora

I just dumped my IPhone for an EVO. I wanted it to tether my laptop. I know i can tether using IPhone if I jailbroke it. Why tether on AT&T’s awful network.

So I wrote a simple script for the EVO (should work on most androup 2.x phones also)

The first thing you want to is enable usb debugging mode on your phone

Settings -> Applications -> Development -> USB Debugging

Now you can download the following file as root

http://zcentric.com/tether.sh

Then run it

sh tether.sh

Then on your phone run the azilink application and start the service. Then you can run

service android-tether start

You should be all nice an tethered to your phone using your USB cable.

WSGI Holygrail run multiple versions of Python on same webserver

One issue hosting providers have is the issue with clients needing to run different version of python. One client might need python 2.4 and others might need 2.5. So in comes uWSGI

So this blog post will go over how to install uWSGI on RedHat 5 and making a uWSGI server for both python 2.4 and 2.5 at the same time for different hosts.

So the first thing you want to do is download the source

wget http://projects.unbit.it/downloads/uwsgi-0.9.6.tar.gz
tar zxfv uwsgi-0.9.6.tar.gz
cd uwsgi-0.9.6

Now make the 2.4 version

make -f Makefile.Py24
mv uwsgi uwsgi24

Now make the 2.5 version

make -f Makefile.Py25
mv uwsgi uwsgi25

Now you have two binaries compiled to run 2.4 and 2.5 for python

So now you run the binaries

./uwsgi -s 127.0.0.1:3031 --pythonpath /tmp/multi/ -w django_wsgi

This will run a uWSGI server for a django app that is in /tmp/multi with a django_wsgi.py file.

You can then run

./uwsgi25 -s 127.0.0.1:3032 --pythonpath /tmp/multi/ -w django_wsgi

At the same time and they play nicely.

My django_wsgi.py file looks like

import os
import sys
sys.path.append('/tmp/multi')
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

Find the real MongoDB command for pyMongo

I am in the middle of writing a MongoDB plugin for cacti using pymongo and ran into a little issue.

In the Mongo shell this works

> db.stats()
{
        "collections" : 19,
        "objects" : 145971,
        "dataSize" : 64785380,
        "storageSize" : 129544960,
        "numExtents" : 75,
        "indexes" : 69,
        "indexSize" : 25403392,
        "ok" : 1
}

So in pymongo I tried

info = db.command("stats")

Then I got the following traceback

Traceback (most recent call last):
  File "./get_mongodb_stats.py", line 68, in 
    main(sys.argv[1:])
  File "./get_mongodb_stats.py", line 39, in main
    get_stats(host, port)
  File "./get_mongodb_stats.py", line 57, in get_stats
    info = db.command("stats")
  File "/usr/lib/python2.6/site-packages/pymongo/database.py", line 306, in command
    (command, result["errmsg"]))

Then I remembered a nice part about the mongoshell. You can run the command without the () and see the function

> db.stats  
function () {
    return this.runCommand({dbstats:1});
}

So you do this in pymongo

info = db.command("dbstats")

MySQL to MongoDB Migration Example

There’s a lot of buzz right no for noSQL solutions and one of the big ones is MongoDB. I was lucky enough to be sent to MongoNYC conference a month or so ago and it opened my eyes to why noSQL is the way to go for most web applications. As the web is more and more based on user contributions our databases are getting more writes put into them which doesn’t scale well in MySQL.

So Photoblog was suffering from slow database performance. The only way to solve this problem was scale up the hardware running it. Now that costs a lot of money so my idea was lets switch from MySQL to MongoDB! So below is an example of how I took 2 tables and made them into one collection in MongoDB.

So an example of our two tables in MySQL

members


CREATE TABLE `members` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(255) NOT NULL DEFAULT '',
  `password` varchar(255) NOT NULL DEFAULT '',
  `email` varchar(60) NOT NULL DEFAULT '',
  KEY `email` (`email`),
  KEY `username_2` (`username`),
)

friends


CREATE TABLE `friends_ng` (
  `userLink` mediumint(8) NOT NULL,
  `friendLink` mediumint(8) NOT NULL,
  `status` tinyint(1) NOT NULL DEFAULT '0',
  `reason` varchar(500) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `timestamp` int(15) NOT NULL,
  `start` tinyint(1) NOT NULL DEFAULT '0',
  KEY `friendLink` (`friendLink`,`userLink`,`status`),
  KEY `userLink` (`userLink`)
)

Now friends was a bit de-rationalized in the first place that is why i have the start column. I found this to be faster then having it completely rational.

So now the fun part is turning it into a MongoDB document to add to our user collection. For those not up to speed with MongoDB you can think of a collection in MongoDB like a table in MySQL and a document as a row. A document in MongoDB is made up of a json array.

So here is what we want to end up with. This is an example taken from my username document.


{
        "_id" : ObjectId("4c21058b7f8b9acf39020000"),
        "email" : "mike@zcentric.com",
        "friends" : [
                ObjectId("4c21058b7f8b9acf39000000"),
                ObjectId("4c21058b7f8b9acf39030000")
        ],
        "friends_pending" : [ ],
        "password" : ".........",
       "username" : "mikezupan",
}

So there are a few differences here.

  • What are those _id’s?
  • Why add friends and friends_pending?

Well _id’s in MongoDB are like unique values for referencing your document. Think of them as an auto-incrementing primary key in MySQL. Well except they aren’t auto-incremented they are more random.

Friends and friends_pending are now in the user object for easy look ups and they are a reference to the friends _id. My friends_pending is empty since I have no friends pending.

So now on to the fun stuff with pulling out data.

So a common query is list all the friends you have. I decided to do this in 2 queries since its a pretty cheap query for my dataset. There are a lot of ways of making a document better where you can do it in a single query but I like this way.

I will be doing this via the MongoDB shell but its the same for any MongoDB driver.

So lets first pull out the user based on username. Usually I’ll set their _id object in a session so I can pull out the user with that.

var u = db.users.findOne({'username': 'mikezupan'})

Now lets find all the users friends

db.users.find({'_id': {'$in': u['friends']}})

It is as simple as that! Have fun with MongoDB, I know I am in my free time.

Django fix for ‘User’ object has no attribute ‘backend

I was trying to auto login a user if I had their user object and was getting this error when I just called something like

login(request, user)

Well this is due to Django wanting to make sure you auth the user first. If you want to bypass this you can just use the following before login

user.backend = 'django.contrib.auth.backends.ModelBackend'

This might not be the best solution but it works

Reason #4 on why Django is great

Middleware!

I can’t tell you how great middleware is in Django. It makes life so easy! Here is a good example. Django doesn’t handle ajax errors very good. It prints them to the client. Sure I can use firebug but I mainly use chrome now and don’t like to touch firefox anymore. So I wrote a bit of middleware to only print out errors in all ajax requests if the app was in DEBUG mode.


from django.conf import settings

import traceback

class AjaxErrorMiddleware:
    def process_exception(self, request, exception):
        if request.META.has_key('HTTP_X_REQUESTED_WITH') and settings.DEBUG:
            print traceback.format_exc() 

I have added this to my Github project also for all my middleware. You just load it by adding it to your middleware section in your settings.py

Reason #3 on why Django is great

Making custom model fields are a great thing to save a lot of time. If you need a lot of custom validation around your modelforms and don’t want to do a lot of copy and pasting then create a custom model field.

For example, I have the need for a user to input a lot of hostnames and domains and got sick of doing custom validation in each model form I created. So I created a model field for it.

So here it is


class HostnameField(models.CharField):
    def clean(self, value):
        value = super(HostnameField, self).clean(value)
        
        import re
        regex = re.compile("^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$")
        r = regex.search(string)
        if len(r.groups()) == 0:
            raise models.ValidationError("You need to enter a valid hostname/domain")
         
        return value

The regex might not be the best, but it seems to cover all the use cases I tried on it.

Next Page »