Tuesday, March 27, 2012

Samba4 DNS sprint, day 2

Ok, so I cheated a bit and kept poking at the DNS forwarder code a bit more yesterday after posting my summary. I didn't quite get anywhere final before I went to bed, but this morning, while waiting for my coffee to run through the machine, I got this thing set up. I now can forward requests the internal server doesn't feel responsible for to another DNS server and get the reply back to the client. :) It's not quite production-ready code, but it sure works good enough to switch my DNS settings on my development machine to use Samba DNS.

That makes today TSIG-day. Time to re-read RFC2845 and see if I can get this implemented in my test client.

Monday, March 26, 2012

Samba4 DNS sprint, day 1 summary

Ok, of course this didn't go as planned. It took longer than expected to figure out how to best test my DNS library, which by itself seems to work ok but also only is a thin wrapper around tdgram, so it doesn't do anything fancy yet.

I played with getting some code into the server, but I think I'm not quite doing the right thing there yet. I've set myself a deadline until tomorrow 11:00, if I haven't got it by then, I'm back to TSIG et al.

All in all, I notice that with all the python programming I've been doing recently, my C-fu has rusted a bit. I hope today will prove to be the WD-40 I needed to get going again. :)

Oh well, enough for today, more Samba DNS work will come tomorrow.

Samba4 DNS sprint, day 1

Samba has it's own small DNS server built in, but it's still lacking a couple of very nice-to-have features. This week, I'll be trying to get as many of those in as possible. There's two big parts here. One is getting forwarder support, so we can query other name servers on behalf of our clients. The other big item is getting signed updates to work so windows clients can sign their dynamic update requests. My battle plan for this week is:
  • Have a quick stab at a really simple forwarder library, but fall back to running dnsmasq with forwarding set up if I don't get anywhere until early afternoon today
  • Implement shared secret TSIG updates, to get the TSIG logic sorted out
  • Implement TKEY exchanges as specified in RFC2930, to set up the TKEY handling infrastructure
  • Make GSS-TSIG work as a possible signing method, so Windows is happy finally
  • More work on the forwarder library if needed/I have the time
Let's see how far I'll get, I'll post another update with what I accomplished today in the evening.

Friday, March 16, 2012

Running Samba's autobuild.py

Samba has a lot of tests, and we like to run them often. In order to easily do that, we've got a script that checks out a bunch of repositories and runs all tests in them, in parallel and independent of each other. It's living in the source tree at scripts/autobuild.py. Here's my notes for running autobuild.py on a local machine. First, set up an in-memory file system. autobuild.py and the tests run by it touch a lot of files, and not running these tests on a spinning disk will speed things up a lot.
# create the memdisk location
mkdir /memdisk

# default size is half your ram, use -o size=SIZE
# to change that if needed
mount -t tmpfs tmpfs /memdisk

# now create an image file, samba's tests don't like plain tmpfs
# Needs to be bigger than 3 gig
dd if=/dev/zero of=/memdisk/build.img bs=1MiB count=4000
losetup /dev/loop0 /memdisk/build.img


# format as ext2, no need to do journalling
# it's gone when the machine fails anyway
mkfs.ext2 /dev/loop0

# mount
mkdir /memdisk/kai
mount /dev/loop0 /memdisk/kai
chown -R kai:kai /memdisk/kai
And now, I can just run ./script/autobuild.py and get a coffee while all the tests are run.

Thursday, December 8, 2011

Python Regexes: Named Groups. Cool Bananas

I'm currently writing a Python parser for GenBank files. I know BioPython has one, and it doesn't even suck, but BioPython requires a bunch of C extensions, so I can't go and just ship it with my Python application. So I'm creating a BioPython-compatible API for the classes that I need for antiSMASH, without the dependency tail BioPython forces on me. Having contributed to BioPerl before, I do like to use regular expressions for token-based parsers, especially as I'm not too fond of lexers in Python. Now, a GenBank header is a peculiar thing that stems from the punch card ages, with a fixed-width format. Unfortunately, at some point that format changed, and things moved around. And there's a ton of programs out there that produce GenBank files that are slightly off. So parsing the header using a token-based approach seems like a good thing. Now, let's look at the first line. That has a bunch of interesting information.
LOCUS       SCU49845     5028 bp    DNA             PLN       21-JUN-1999
The 'LOCUS' tag explains what this line is about, then there's the accession number for the sequence, the length of the sequence, the type of the molecule, some classification where the sequence comes from, and the date the sequence last saw a major update. The regex to parse this is pretty straightforward (yeah, right):
LOCUS\s+([\w.]+)\s+(\d+)\sbp\s(ss-|ds-|ms-|\s{3,3})(\S{2,4})\s+(linear\s\s|circular|\s{7,7})\s+(\w{3,3})\s+(\d\d-\w{3,3}-\d{4,4})
Incidently, this is a great example for why people dislike regular expressions. Now, in both Perl and Python, there's a way to define verbose regular expressions, so you can restate the regular expression as:
LOCUS\s+        # Header line starts with LOCUS tag followed by multiple spaces
(               # accession number regex:
  [\w.]+        # any alphanumeric character or '.'
)
\s+             # skip over whitespace
(               # sequence length
  \d+           # digits only
)\sbp\s         # skip ' bp ' string
(               # single, double or mixed stranded or nothing
  ss-|ds-|ms-|\s\s\s # can be all spaces
)
(               # Molecule type DNA, RNA, rRNA, mRNA, uRNA
  \S+
)
\s+
(               # linear, circular or seven spaces
  linear|circular|\s{7,7}
)\s+
(               # division code, three characters
 \w{3,3}
)
\s+
(               # date, in dd-MMM-yyyy
  \d\d-\w{3,3}-\d{4,4}
)
This is already pretty decent, but Python can do one better. With the current version of the regex, I need to remember that the molecule type is the 4th group, so match.group(3) will be what I'm looking for. Python decided to extend the Perl extension syntax a bit more, to add named groups. With ?P<name> you can name groups. and then call match.group('name') to access them later. So the final version of the parser regex turns into
LOCUS\s+        # Header line starts with LOCUS tag followed by multiple spaces
(?P<accession>  # accession number regex:
  [\w.]+        # any alphanumeric character or '.'
)
\s+             # skip over whitespace
(?P<length>     # sequence length
  \d+           # digits only
)\sbp\s         # skip ' bp ' string
(?P<stranded>   # single, double or mixed stranded or nothing
  ss-|ds-|ms-|\s\s\s # can be all spaces
)
(?P<molecule>   # Molecule type DNA, RNA, rRNA, mRNA, uRNA
  \S+
)
\s+
(?P<formation>  # linear, circular or seven spaces
  linear|circular|\s{7,7}
)\s+
(?P<division>   # division code, three characters
 \w{3,3}
)
\s+
(?P<date>       # date, in dd-MMM-yyyy
  \d\d-\w{3,3}-\d{4,4}
)
and you can use speaking names to access the group's contents later. Great to make the code more readable. Combined with a bunch of tests, that should stay maintainable.

Friday, August 12, 2011

From the frontline, day 5

Another day, another piece of testing mayhem. I've completed the 0.1 version of my Flask-Downloader helper class. With this, I could complete my web app. Now, the downloader itself has a bunch of tests to make sure it's working as expected, but I was also going to test the corresponding code paths in the web app's tests.
The user can provide the input either by uploading a file or by giving an accession number. Testing for the file uploads was easy, as the Flask test client accepts file-like objects as data input for POST requests. So testing the app will do the right thing is as easy as:
def test_upload(self):
    file_handle = open(tmp_filename)
    data = dict(file=file_handle)
    rv = self.client.post('/upload', data=data)
    assert "upload succeeded" in rv.data
Assuming your upload function listens on '/upload' and returns a page that contains "upload ducceeded", of course.
Testing file downloads is a bit more elaborated, because I don't actually want my downloader to connect to the internet during a test run. Minimock to the rescue! I can fake the download helper and create the same kind of output to fool the application code.
from minimock import Mock
from werkzeug import FileStore
def test_download(self):
    data = dict(id="FAKE")
    # now create the fake downloader
    tmp_file = open(tmp_file_path)
    dl.download = Mock('dl.download')
    dl.download.mock_returns = FileStore(stream=tmp_file)
     rv = self.client.post('/download', data=data)
    assert "download succeeded" in rv.data
With similar assumptions as in the example before, and also the idea that you have a pre-existing file in tmp_file_path. A StringIO file-like object should do the trick as well.
With all the tests in place and a test coverage of 100%, I declare this campaign a success. I still need to deploy the new web app on my test server instead of the old one, but I'm going to do that next week. I will also continue my war on legacy code, now tackling the pieces that do the actual work. No war is over as quick as you'd initially hope after all. Also, I'm pretty sure the 100% code coverage don't mean there's not plenty of places for bugs to hide in, just that at least all of the code is looked at by the interpreter once. Still, it's a good conclusion to a busy week. Testing rocks.

Thursday, August 11, 2011

From the frontline, day 4

Today, I decided to go for the downloader component that can download files on the behalf of the users. While looking at how to test this, I actually noticed that the mm_unit functionality has been merged into the minimock package. Sweet.
I wanted to keep this modular, a downloader sounds like a tool I could use in a couple of projects. So I created a Flask extension. There's a nice wizard script that automates the creation of the boilerplate files. Using the wizard, I created Flask-Downloader. It's pretty straightforward to use. There's a download(url) function that will return a werkzeug.FileStorage instance, just like the flask upload hander. I'll also add a save(url) function that'll save the url's contents to a file without returning a file-like object.
Not too much to write about, spent a lot of time researching stuff today. Hope to get done with my changes tomorrow. Let's see how that'll work out