<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5304945066026788056</id><updated>2025-12-09T03:49:15.436+02:00</updated><category term="Python"/><category term="OpenLDAP"/><category term="iplib"/><category term="pyparsing"/><category term="AppEngine"/><category term="Exim"/><category term="LEG"/><category term="MySQL"/><category term="handle.net"/><category term="reddit"/><title type='text'>Craig Balfour&#39;s Blog</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>14</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-6720007608697949285</id><published>2013-05-14T11:07:00.002+02:00</published><updated>2013-05-14T11:09:22.242+02:00</updated><title type='text'>ScaleConf 2013</title><content type='html'>&lt;br /&gt;
I attended &lt;a href=&quot;http://scaleconf.org/&quot;&gt;ScaleConf&lt;/a&gt; a few weeks back.&lt;br /&gt;
&lt;br /&gt;
It was a really great conference - perhaps the best I have ever attended.&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.sanbi.org/gardens/kirstenbosch&quot;&gt;Kirstenbosch Gardens&lt;/a&gt; is a fantastic venue. I took every opportunity between talks and after lunch to walk around the gardens and just absorb the peacefulness of the place.&lt;/li&gt;
&lt;li&gt;The speakers in general have interesting talks. And it is always interesting to hear from international speakers about the scaling issues they face.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;And the food - both snacks and lunch at &lt;a href=&quot;http://www.moyo.co.za/&quot;&gt;Moyo&lt;/a&gt; - was fantastic.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
Here are a few of themes I took away with me:&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Scale horizontally, not vertically.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Do this by decoupling your service into smaller independent systems and use message queuing (RabbitMQ and friends) to get data to appropriate components.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Use NOSQL/memcache/key-value stores where possible since relational DBs don&#39;t scale well.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;And measure everything so you know when stuff is broken.&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;
I&#39;d highly recommend attending next year if you get the opportunity.&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/6720007608697949285/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/6720007608697949285' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/6720007608697949285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/6720007608697949285'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2013/05/scaleconf-2013.html' title='ScaleConf 2013'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-7955882154275647446</id><published>2013-05-14T10:43:00.002+02:00</published><updated>2013-05-14T10:48:43.880+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="handle.net"/><category scheme="http://www.blogger.com/atom/ns#" term="MySQL"/><title type='text'>Using MySQL as handle.net database back-end</title><content type='html'>&lt;br /&gt;
I&#39;ve recently been working on getting a &lt;a href=&quot;http://handle.net/&quot;&gt;CNRI handle&lt;/a&gt; (aka persistent identifier) server up and running. We wanted to use MySQL as our database back-end. Since the &lt;a href=&quot;http://handle.net/tech_manual/Handle_6_Technical_Manual.pdf&quot;&gt;documentation&lt;/a&gt; didn&#39;t cover how to do this in sufficient detail so here is what I did.&lt;br /&gt;
&lt;br /&gt;
PS: We are using a pretty old version of the handle.net software - v 6.2. Hopefully this configuration still applies to the newer versions.&lt;br /&gt;
&lt;h2&gt;
Background&lt;/h2&gt;
By default the handle.net software stores its data in a Java jdb database. In my opinion this is less than ideal since there are no tools to manipulate or even view your data. You can, I guess, write your own java code to do this, but this is a pain. &lt;br /&gt;
&lt;br /&gt;
Everybody knows MySQL so we wanted to use it as our back-end. The handle server does support SQL but the document only provides a config example for postgres. (Strangely, it does provide a table layout for MySQL) Google also didn&#39;t provide any useful answers.&lt;br /&gt;
&lt;br /&gt;
So, in case it is useful to anybody else, here is my configuration.&lt;br /&gt;
&lt;h2&gt;
Setup MySQL&lt;/h2&gt;
Install the MySQL server package. Use apt-get or yum depending on your linux distribution.&lt;br /&gt;
&lt;br /&gt;
Create a database. I decided to call mine &lt;b&gt;handle&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNo1ewZGeFUvTxHzL_-bKsISgZZyJLDabH5Ti-xFkBg1Ddwns7MCJnRpYoPCosRNtEd2tiE9kG27QNoxyrAFGCfLwXIYCcIOevdhQT0mam0OkFo9LFumZL9obynuaLSC_9ob3NmTMKvaXL/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;&quot;&gt;&lt;code style=&quot;color: black; word-wrap: normal;&quot;&gt; mysqladmin -u root -p create handle  
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Create a MySQL user and give it access to the database.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNo1ewZGeFUvTxHzL_-bKsISgZZyJLDabH5Ti-xFkBg1Ddwns7MCJnRpYoPCosRNtEd2tiE9kG27QNoxyrAFGCfLwXIYCcIOevdhQT0mam0OkFo9LFumZL9obynuaLSC_9ob3NmTMKvaXL/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;&quot;&gt;&lt;code style=&quot;color: black; word-wrap: normal;&quot;&gt; grant all on handle.* to handle@&#39;localhost&#39; identified by &#39;supersecretpassword&#39;;  
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Setup the table structure as per the MySQL example in the documentation.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNo1ewZGeFUvTxHzL_-bKsISgZZyJLDabH5Ti-xFkBg1Ddwns7MCJnRpYoPCosRNtEd2tiE9kG27QNoxyrAFGCfLwXIYCcIOevdhQT0mam0OkFo9LFumZL9obynuaLSC_9ob3NmTMKvaXL/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;&quot;&gt;&lt;code style=&quot;color: black; word-wrap: normal;&quot;&gt; create table nas (  
   na varchar(255) not null,  
   PRIMARY KEY(na)  
 ) ;  
 create table handles (  
   handle varchar(255) not null,  
   idx int4 not null,  
   type blob,  
   data blob,  
   ttl_type int2,  
   ttl int4,  
   timestamp int4,  
   refs blob,  
   admin_read bool,  
   admin_write bool,  
   pub_read bool,  
   pub_write bool,  
   PRIMARY KEY(handle, idx)  
 );  
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Download MySQL&#39;s &lt;a href=&quot;http://dev.mysql.com/downloads/connector/j/&quot;&gt;JDBC jconnector&lt;/a&gt; and put it somewhere useful. I put it in &lt;i&gt;/opt/mysql-connector-java-5.1.25&lt;/i&gt;. Uncompress the archive and you should end up with a sub-directory all sorts of stuff but most importantly a jar file - in my case,&amp;nbsp;mysql-connector-java-5.1.25-bin.jar.&lt;br /&gt;
&lt;h2&gt;
Configure Handle Software&lt;/h2&gt;
I assume that you already have it up and running using the Java jdb database. If not follow the documentation and get this working first.&lt;br /&gt;
&lt;br /&gt;
I&#39;ll just describe what I did to change from the local jdb database to MySQL.&lt;br /&gt;
&lt;br /&gt;
Edit your startup script to add the jconnector JAR to your classpath.&lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNo1ewZGeFUvTxHzL_-bKsISgZZyJLDabH5Ti-xFkBg1Ddwns7MCJnRpYoPCosRNtEd2tiE9kG27QNoxyrAFGCfLwXIYCcIOevdhQT0mam0OkFo9LFumZL9obynuaLSC_9ob3NmTMKvaXL/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;&quot;&gt;&lt;code style=&quot;color: black; word-wrap: normal;&quot;&gt; #!/bin/bash  
 java_path=/opt/jdk1.7.0_21/bin  
 $java_path/java -cp &quot;/hs/bin/handle.jar:/opt/mysql-connector-java-5.1.25/mysql-connector-java-5.1.25-bin.jar&quot; net.handle.server.Main /hs/svr_1  
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
Notice the colon (:) between the two paths which make up your classpath (cp). If you are running this on Windows you need to use a semi-colon (;) delimiter.&lt;br /&gt;
&lt;br /&gt;
Update your &lt;i&gt;config.dct&lt;/i&gt;. Add the following config snippet to the existing &quot;server_config&quot; section. &lt;br /&gt;
&lt;br /&gt;
&lt;pre style=&quot;background-image: URL(https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiNo1ewZGeFUvTxHzL_-bKsISgZZyJLDabH5Ti-xFkBg1Ddwns7MCJnRpYoPCosRNtEd2tiE9kG27QNoxyrAFGCfLwXIYCcIOevdhQT0mam0OkFo9LFumZL9obynuaLSC_9ob3NmTMKvaXL/s320/codebg.gif); background: #f0f0f0; border: 1px dashed #CCCCCC; color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;&quot;&gt;&lt;code style=&quot;color: black; word-wrap: normal;&quot;&gt; &quot;storage_type&quot; = &quot;sql&quot;  
 sql_settings = {  
  sql_url = &quot;jdbc:mysql://localhost:3306/handle&quot;  
  sql_driver = &quot;com.mysql.jdbc.Driver&quot;  
  sql_login = &quot;handle&quot;  
  sql_passwd = &quot;supersecretpassword&quot;  
  sql_read_only = &quot;no&quot;}  
  }  
&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
The things to note - which differ from the Postgres example in the docs - is the &lt;b&gt;sql_url&lt;/b&gt; and &lt;b&gt;sql_driver&lt;/b&gt;.&lt;br /&gt;
&lt;br /&gt;
Fire up your handle server and make sure it starts properly. If you get a MySQL error the server either cannot find the jar file (check your classpath) or something in your&lt;i&gt; config.dct&lt;/i&gt; is wrong.&lt;br /&gt;
&lt;br /&gt;
Fire up the handle admin tool and use it to re-home your server by clicking &lt;i&gt;Server Admin&lt;/i&gt; | &lt;i&gt;Home Naming Authority&lt;/i&gt;. This will put your naming authority into the nas table.&lt;br /&gt;
&lt;br /&gt;
And you&#39;re done!&lt;br /&gt;
&lt;br /&gt;
Of course, your handle table is now completely empty and you will need to get your digital repository to re-populate it. (Unless of course you are up to writing your own java code to read the existing data our of the jdb and putting it into the MySQL handle table).&lt;br /&gt;
&lt;br /&gt;
In our case - where we use &lt;a href=&quot;http://www.exlibrisgroup.com/category/DigiToolOverview&quot;&gt;Digitool&lt;/a&gt; - it was just a matter of running a &lt;i&gt;Publish Persistent Identifier&lt;/i&gt; job and all of our handles got republished into the MySQL tables.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/7955882154275647446/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/7955882154275647446' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/7955882154275647446'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/7955882154275647446'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2013/05/ive-recently-been-working-on-getting.html' title='Using MySQL as handle.net database back-end'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-3390989370813278480</id><published>2008-10-10T11:51:00.003+02:00</published><updated>2008-10-10T12:38:40.309+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Normalizing a MAC address string</title><content type='html'>Over the last few days, I have been spending some time working on my &lt;a href=&quot;http://www.python.org/&quot;&gt;python&lt;/a&gt; - reading the sections of &lt;a href=&quot;http://www.diveintopython.org/&quot;&gt;Diving into Python&lt;/a&gt; that I have never got around to and &lt;a href=&quot;http://en.wikipedia.org/wiki/Refactoring&quot;&gt;refactoring&lt;/a&gt; parts of some of my python scripts to make better use of the features of language and, ultimately, to make them more robust (i.e. usable by people other than me).&lt;br /&gt;&lt;p&gt;The script I have started with is a simple one for registering hosts for DHCP access. Basically, it takes two command line arguments - a fully qualified hostname and a MAC address - and then does some validation, checks that neither address is already in use, normalizes the output to the correct format, constructs a properly formatted host stanza and appends it to the end of our ISC DHCP servers dhcpd.conf configuration file.&lt;/p&gt;I have made improvements to various parts of the code but the changes I am most conflicted about are those I have made to the MAC address normalization function which works reliably and therefore probably isn&#39;t a good candidate for refactoring but (to me anyway) looks inelegant - something which I think matters in an elegant language like python.&lt;br /&gt;&lt;br /&gt;The normalization function takes as inpute a MAC address in one of three (string) formats - unix  (00:11:22:33:aa:55), Windows(00-11-22-33-AA-55) and Cisco (0011.2233.aa55) - and the same MAC address in the unix format.&lt;br /&gt;&lt;p&gt;Since I am trying to move towards a more &lt;a href=&quot;http://www.google.co.za/url?sa=t&amp;amp;source=web&amp;amp;ct=res&amp;amp;cd=1&amp;amp;url=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FTest-driven_development&amp;amp;ei=LS3vSNmULYHm1gaTyPC9Bw&amp;amp;usg=AFQjCNGw3vES1hPm5nceb6amq46KwQ1yaA&amp;amp;sig2=DznR8VpidIAfCvgyrSbIiQ&quot;&gt;test-driven development&lt;/a&gt; approach, I started out by writing a very basic unit test to make sure my new function is at least as reliable as the old function. Here is the code on the unit test (test.py):&lt;br /&gt;&lt;/p&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;#!/usr/bin/env python&lt;br /&gt;import unittest&lt;br /&gt;import sys&lt;br /&gt;sys.path.append(&quot;.&quot;)&lt;br /&gt;import oldmac as mac&lt;br /&gt;#import newmac as mac&lt;br /&gt;&lt;br /&gt;class Test(unittest.TestCase):&lt;br /&gt; &quot;&quot;&quot; Unit test/s for MAC address normalization function &quot;&quot;&quot;&lt;br /&gt; normalize_values = (&lt;br /&gt;  (&#39;00:11:22:33:aa:55&#39;, &#39;00:11:22:33:aa:55&#39;),&lt;br /&gt;  (&#39;0011.2233.aa55&#39;, &#39;00:11:22:33:aa:55&#39;),&lt;br /&gt;  (&#39;00-11-22-33-aa-55&#39;, &#39;00:11:22:33:aa:55&#39;),&lt;br /&gt;  (&#39;00:11:22:33:AA:55&#39;, &#39;00:11:22:33:aa:55&#39;)&lt;br /&gt; )&lt;br /&gt;&lt;br /&gt; def testMacNormalize(self):&lt;br /&gt;  &quot;&quot;&quot; Normalize MAC addresses to lowercase unix format &quot;&quot;&quot;&lt;br /&gt;  for addr, expected in self.normalize_values:&lt;br /&gt;   result = mac.normalize(addr)&lt;br /&gt;   self.assertEqual(expected, result)&lt;br /&gt;&lt;br /&gt;if __name__ == &quot;__main__&quot;:&lt;br /&gt; unittest.main()&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;Here is my orginal normalization function (oldmac.py):&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;#!/usr/bin/env python&lt;br /&gt;&quot;&quot;&quot; Old MAC normalization function &quot;&quot;&quot;&lt;br /&gt;import re&lt;br /&gt;&lt;br /&gt;def normalize(m):&lt;br /&gt; &quot;&quot;&quot; Normalize a MAC address to lower case unix style &quot;&quot;&quot;&lt;br /&gt; m = re.sub(&quot;[.:-]&quot;, &quot;&quot;, m)&lt;br /&gt; m = m.lower()&lt;br /&gt; n =  &quot;%s:%s:%s:%s:%s:%s&quot; % (m[0:2], m[2:4], m[4:6], m[6:8], m[8:10], m[10:])&lt;br /&gt;      return n&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;There are two things I don&#39;t like about this code:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Use of a regular expression for something as simple as eliminating the delimiters from the string. There must be a simpler way to do this.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;And the bit I find inelegant - the construction of the normalised string, n.  It looks ugly :)&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;First off, here is the results of running my unit tests against this old function:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$ ./test.py -v&lt;br /&gt;Normalize MAC addresses to lowercase unix format ... ok&lt;br /&gt;----------------------------------------------------------------------&lt;br /&gt;Ran 1 test in 0.000s&lt;br /&gt;OK&lt;br /&gt;&lt;/pre&gt;So, I set out to rewrite the normalize function more elegantly. Here is the result (newmac.py):&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;#!/usr/bin/env python&lt;br /&gt;&quot;&quot;&quot; New MAC normalization function &quot;&quot;&quot;&lt;br /&gt;&lt;br /&gt;def normalize(addr):&lt;br /&gt;&lt;br /&gt; # Determine which delimiter style out input is using&lt;br /&gt; if &quot;.&quot; in addr:&lt;br /&gt;  delimiter = &quot;.&quot;&lt;br /&gt; elif &quot;:&quot; in addr:&lt;br /&gt;  delimiter = &quot;:&quot;&lt;br /&gt; elif &quot;-&quot; in addr:&lt;br /&gt;  delimiter = &quot;-&quot;&lt;br /&gt;&lt;br /&gt; # Eliminate the delimiter&lt;br /&gt; m = addr.replace(delimiter, &quot;&quot;)&lt;br /&gt;&lt;br /&gt; m = m.lower()&lt;br /&gt;&lt;br /&gt; # Normalize&lt;br /&gt; n= &quot;:&quot;.join([&quot;%s%s&quot; % (m[i], m[i+1]) for i in range(0,12,2)])&lt;br /&gt;&lt;br /&gt; return n&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;The differences between this version and old version are:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;I replaced the regular expression with a simple string.replace. Why use a (something-big) when a (something-small) will do.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;I replaced the normalization expression with does the same thing but using a &lt;a href=&quot;http://en.wikipedia.org/wiki/List_comprehension&quot;&gt;list comprehension&lt;/a&gt;.&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ol&gt;And my unit test runs against this version:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;$./test.py -v&lt;br /&gt;Normalize MAC addresses to lowercase unix format ... ok&lt;br /&gt;----------------------------------------------------------------------&lt;br /&gt;Ran 1 test in 0.000s&lt;br /&gt;OK&lt;br /&gt;&lt;/pre&gt;So, what do you think? An improvement or not?&lt;br /&gt;&lt;p&gt;Have I over-engineered the normalization code by replacing something static, simple and fast with something more complex, less readable and probably slower?&lt;br /&gt;&lt;/p&gt;Is there a better way of normalizing a MAC address string?&lt;br /&gt;&lt;br /&gt;&lt;p&gt;Note: I have just presented one aspect of the refactoring on this script - the MAC address normalization. This is the part I am most conflicted about because the old code worked fine but the new code looks &quot;cooler&quot; :) . The other improvements such as the more robust validation I have left out (perhaps for another blog post). &lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/3390989370813278480/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/3390989370813278480' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/3390989370813278480'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/3390989370813278480'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/10/normalizing-mac-address-string.html' title='Normalizing a MAC address string'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-5366290633645403782</id><published>2008-08-28T10:31:00.004+02:00</published><updated>2008-08-28T10:39:26.456+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="reddit"/><title type='text'>A few days of relative fame</title><content type='html'>Here&#39;s what happens when somebody posts a few things you&#39;ve written about / in python to &lt;a href=&quot;http://www.reddit.com/r/python&quot;&gt;reddit.com&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyfs9iNiV3JaVOaUZq3qEMcoHsR0sBClZFB5nF2iSiCpi6hVKVJpVl-OOgMvmP4IcytEFyXJCITB0DgwPhus3QASoqcTjwm6VvIaktBeOeTcowT3BLY69omYjJz7VSR5jJGMHKCwmgx64/s1600-h/python_ip_sort.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyfs9iNiV3JaVOaUZq3qEMcoHsR0sBClZFB5nF2iSiCpi6hVKVJpVl-OOgMvmP4IcytEFyXJCITB0DgwPhus3QASoqcTjwm6VvIaktBeOeTcowT3BLY69omYjJz7VSR5jJGMHKCwmgx64/s400/python_ip_sort.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5239483776459170674&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/5366290633645403782/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/5366290633645403782' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/5366290633645403782'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/5366290633645403782'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/08/few-days-of-relative-fame.html' title='A few days of relative fame'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyfs9iNiV3JaVOaUZq3qEMcoHsR0sBClZFB5nF2iSiCpi6hVKVJpVl-OOgMvmP4IcytEFyXJCITB0DgwPhus3QASoqcTjwm6VvIaktBeOeTcowT3BLY69omYjJz7VSR5jJGMHKCwmgx64/s72-c/python_ip_sort.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-8908865574457696025</id><published>2008-08-25T13:26:00.005+02:00</published><updated>2008-08-25T14:09:29.153+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="iplib"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>A more efficient method of sorting a list of IP addresses in Python</title><content type='html'>As &lt;a href=&quot;https://www.blogger.com/comment.g?blogID=5304945066026788056&amp;amp;postID=152424677927018657&quot;&gt;ben, okplus and tom pointed out&lt;/a&gt; in comments to my &lt;a href=&quot;http://craigbalfour.blogspot.com/2008/08/sorting-list-of-ip-addresses.html&quot;&gt;Sorting a list of IP addresses in Python&lt;/a&gt; blog post, my sort function is very inefficient. As implemented, each IP address gets converted to decimal a number of times which is unnecessary and wasteful. Thinking about the bigger picture  - using the function in an application - it doesn&#39;t make sense to convert the IP address to decimal in the sort function.&lt;br /&gt;&lt;br /&gt;Since my goal was to extend the work of the iplib module, the solution, I think, should involve using this module more efficiently, if possible. The iplib module represents an IPv4 address using its IPv4Address class. When initializing an instance of this class, the address gets converted to decimal and stored in the _ip_dec attribute, accessible by calling its get_dec() method. So by converting the raw IP addresses to IPv4Address instances outside the sort function (which I would probably be doing anyway) and using the get_dec() method in the function should improve things.&lt;br /&gt;&lt;br /&gt;So, here is what I hope is a more efficient sorting function: &lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;&lt;br /&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;import iplib&lt;br /&gt;&lt;br /&gt;ips = []&lt;br /&gt;&lt;br /&gt;for ip in [&quot;192.168.100.56&quot;, &quot;192.168.0.3&quot;, &quot;192.0.0.192&quot;, &quot;8.0.0.255&quot;]:&lt;br /&gt;       ips.append(iplib.IPv4Address(ip))&lt;br /&gt;&lt;br /&gt;def ip_compare(x, y):&lt;br /&gt;       return cmp(x.get_dec(), y.get_dec())&lt;br /&gt;&lt;br /&gt;ips.sort(ip_compare)&lt;br /&gt;&lt;br /&gt;print [ip.address for ip in ips]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Of course, comments are welcome.&lt;br /&gt;&lt;br /&gt;Thanks to  ben, okplus and tom for their helpful comments to my previous attempt.</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/8908865574457696025/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/8908865574457696025' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/8908865574457696025'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/8908865574457696025'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/08/more-efficient-method-of-sorting-list.html' title='A more efficient method of sorting a list of IP addresses in Python'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-6781553653676835830</id><published>2008-08-22T17:19:00.008+02:00</published><updated>2008-08-28T10:17:03.029+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><title type='text'>Playing with Google AppEngine</title><content type='html'>I&#39;ve just spent the last two days playing around with Google&#39;s &lt;a href=&quot;http://appengine.google.com/&quot;&gt;AppEngine&lt;/a&gt;, and I like it.&lt;br /&gt;&lt;br /&gt;When my language of choice was &lt;a href=&quot;http://www.perl.org/&quot;&gt;perl&lt;/a&gt;, I wrote plain ole CGI web applications (if you can call them that) using perl&#39;s &lt;a href=&quot;http://search.cpan.org/dist/CGI.pm/&quot;&gt;CGI module&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the &lt;a href=&quot;http://www.python.org/&quot;&gt;python&lt;/a&gt; world though, web frameworks and &lt;a href=&quot;http://www.python.org/dev/peps/pep-0333/&quot;&gt;WSGI&lt;/a&gt; seem to be the way to do things, so I&#39;ve known for a while that I needed to learn one of the many frameworks out there. I spent about a week getting my head around &lt;a href=&quot;http://www.turbogears.org/&quot;&gt;TurboGears&lt;/a&gt; and the concepts of &lt;a href=&quot;http://en.wikipedia.org/wiki/Object-relational_mapping&quot;&gt;ORM&lt;/a&gt; a year or so ago using the &lt;a href=&quot;http://www.amazon.com/Rapid-Web-Applications-TurboGears-Ajax-Powered/dp/0132433885/ref=sr_1_1?ie=UTF8&amp;amp;s=books&amp;amp;qid=1219422023&amp;amp;sr=1-1&quot;&gt;TurboGears book&lt;/a&gt;. After a week I has finished the book and has the sample app working but had no more time left to spend on the project. Obviously, not using it, I&#39;ve long since forgotten all of the TurboGears specific stuff. I had through to given &lt;a href=&quot;http://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; my next try but AppEngine caught my eye in the meantime.&lt;br /&gt;&lt;br /&gt;AppEngine has a nice &lt;a href=&quot;http://code.google.com/appengine/docs/gettingstarted/&quot;&gt;Getting Started Guide&lt;/a&gt; which I used to get a simple little application up and running on the local development web server included in the &lt;a href=&quot;http://code.google.com/appengine/downloads.html&quot;&gt;SDK&lt;/a&gt; in an hour or two.&lt;br /&gt;&lt;br /&gt;Learning from the mistakes of the past, I decided on a project - porting a perl CGI script I wrote a few years ago (&lt;a href=&quot;http://nom.uct.ac.za/cgi-bin/map.cgi&quot;&gt;a University of Cape Town map&lt;/a&gt; using the&lt;a href=&quot;http://code.google.com/apis/maps/&quot;&gt; Google Maps API&lt;/a&gt; and some local data I had gathered) to AppEngine. The result is basic but its a start and its &lt;a href=&quot;http://uctmap.appspot.com/&quot;&gt;here&lt;/a&gt;. Hopefully, over time I&#39;ll develop it into something useful - maybe add some additional information such as a picture or two, a brief description, a list of departments, etc to each building.&lt;br /&gt;&lt;br /&gt;The only problem I has was with bulkload_client.py, a tool for reading bulk data from a CSV file and importing it into the application. We are behind a proxy server, and bulkload_client.py doesn&#39;t support proxying (even though application publishing tool does). Since everything is written in python adding (or should I rather say hacking in) proxy support was not a big problem. Just edit google/appengine/tools/bulkload_client.py in the SDK and change line 136 from:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;connection = httplib.HTTPConnection(host_port)&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;to&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;connection = httplib.HTTPConnection(&quot;127.0.0.1&quot;, 3128)&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;where &quot;127.0.0.1&quot; is the IP address of your proxy server and 3128 is the proxy port, and change line 138 from:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;connection.request(&#39;POST&#39;, uri, body, headers)&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;to:&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;connection.request(&#39;POST&#39;, &#39;http://%s%s&#39; % (host_port, uri), body, headers)&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;and it should use your proxy server to upload your data to your application. When I get a chance I will try add proxy support using &lt;a href=&quot;http://docs.python.org/lib/module-getopt.html&quot;&gt;getopt&lt;/a&gt; and produce a proper patch - maybe :)&lt;br /&gt;&lt;br /&gt;Of course, this is only needed when you have deployed your app to the Google AppEngine infrastructure and you want to deploy your data, not when you&#39;re using the development server included in the SDK.&lt;br /&gt;&lt;br /&gt;And that&#39;s all there is too it. AppEngine is still in beta and is limited to 10 applications per Google Account (and you can&#39;t currently delete your applications once you&#39;ve deployed them - though this feature is apparently coming) but when it goes live it&#39;s going to be big. Hopefully they still allow small applications to use the infrastructure for free.&lt;br /&gt;&lt;br /&gt;Next stop, &lt;a href=&quot;http://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/6781553653676835830/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/6781553653676835830' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/6781553653676835830'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/6781553653676835830'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/08/playing-with-google-appengine.html' title='Playing with Google AppEngine'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-152424677927018657</id><published>2008-08-01T13:11:00.006+02:00</published><updated>2008-08-28T10:15:47.756+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="iplib"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Sorting a list of IP addresses in Python</title><content type='html'>As I work a lot with network data, one of my favourite &lt;a href=&quot;http://www.python.org/&quot;&gt;python&lt;/a&gt; modules is&lt;a href=&quot;http://erlug.linux.it/%7Eda/soft/iplib&quot;&gt; iplib&lt;/a&gt;. It takes care of quite a few of things I want to do with IP addresses but lacks a lot of functionality of perl&#39;s Net::Netmask which I relied on extensively when perl was my favourite language.&lt;br /&gt;&lt;br /&gt;One of the iplib missing features is a method for sorting a list of IP addresses, or at the very least, a method for comparing two addresses.&lt;br /&gt;&lt;br /&gt;Luckily this is easy enough to implement yourself in python using a customised sort function. See the &lt;a href=&quot;http://wiki.python.org/moin/HowTo/Sorting&quot;&gt;Sorting Mini-HOW TO&lt;/a&gt; for a well written document on sorting in python.&lt;br /&gt;&lt;br /&gt;Here is my attempt at a custom function for sorting IP addresses.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;import iplib&lt;br /&gt;&lt;br /&gt;ips = [&quot;192.168.100.56&quot;, &quot;192.168.0.3&quot;, &quot;192.0.0.192&quot;, &quot;8.0.0.255&quot;]&lt;br /&gt;&lt;br /&gt;def ip_compare(x, y):&lt;br /&gt;    &quot;&quot;&quot; Compare two IP addresses. &quot;&quot;&quot;&lt;br /&gt;    # Convert IP addresses to decimal for easy comparison&lt;br /&gt;    dec_x = int(iplib.convert(x, &quot;dec&quot;))&lt;br /&gt;    dec_y = int(iplib.convert(y, &quot;dec&quot;))&lt;br /&gt;    if dec_x &gt; dec_y:&lt;br /&gt;            return 1&lt;br /&gt;    elif dec_x == dec_y:&lt;br /&gt;            return 0&lt;br /&gt;    else:&lt;br /&gt;            return -1&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;ips.sort(ip_compare) # Sort in place&lt;br /&gt;&lt;br /&gt;print ips&lt;br /&gt;[&#39;8.0.0.255&#39;, &#39;192.0.0.192&#39;, &#39;192.168.0.3&#39;, &#39;192.168.100.56&#39;]&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Sorted.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;Update: A few people left comments which helped me realise how inefficient the above approach is.  Take a look at the &lt;/span&gt;&lt;a style=&quot;font-style: italic;&quot; href=&quot;http://craigbalfour.blogspot.com/2008/08/more-efficient-method-of-sorting-list.html&quot;&gt;improved version&lt;/a&gt;&lt;span style=&quot;font-style: italic;&quot;&gt;.&lt;/span&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/152424677927018657/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/152424677927018657' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/152424677927018657'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/152424677927018657'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/08/sorting-list-of-ip-addresses.html' title='Sorting a list of IP addresses in Python'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-6185513352341687468</id><published>2008-07-31T17:09:00.006+02:00</published><updated>2008-07-31T17:35:15.588+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="LEG"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>LEG download stats</title><content type='html'>Over the last few days I&#39;ve been playing around with analyzing and graphing the &lt;a href=&quot;http://www.proftpd.org&quot;&gt;proftpd&lt;/a&gt; and &lt;a href=&quot;http://httpd.apache.org&quot;&gt;Apache&lt;/a&gt; logs from &lt;a href=&quot;http://www.uct.ac.za/&quot;&gt;University of Cape Town&lt;/a&gt; &lt;a href=&quot;http://www.leg.uct.ac.za/&quot;&gt;Linux Enthusiasts Group&lt;/a&gt; mirror server for a report I am writing.&lt;br /&gt;&lt;br /&gt;The data is incomplete but what we have looks quite impressive - and this is only data to UCT IP addresses, not any off-campus data.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZpHF8jGYdM7fSEhuXy9iPhabVXjTNUFThkphMpm1gl-7d-nLHlT-ct4NldKjO_VfNP1kLGZEY79Z64-fvHDpryGc8w3ncUQJmKkLIxEFFnTDIMLJSVEC4WkBKHlSxhSSB11o2FV0-BRk/s1600-h/leg-ftp-barchart.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZpHF8jGYdM7fSEhuXy9iPhabVXjTNUFThkphMpm1gl-7d-nLHlT-ct4NldKjO_VfNP1kLGZEY79Z64-fvHDpryGc8w3ncUQJmKkLIxEFFnTDIMLJSVEC4WkBKHlSxhSSB11o2FV0-BRk/s400/leg-ftp-barchart.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5229197085932491986&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;We have a lot more log information for ftp - incomplete but going back as far as 2000.&lt;br /&gt;&lt;br /&gt;In the past, ftp was the way things were done. We installed linux using a network install over ftp, and we used ftp to fetch updates.&lt;br /&gt;&lt;br /&gt;&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeuLPXSksM7rkMKrMALXks8f_9pMkwyNRDHo0Px8XCHXa19Uum8cUp75mhVfHndP_mRV2o3ywe-_90uOK7T-6yGZ9DPop1BYQ4u1VIoIiFavMsoJ2OJlOhhqKAhW599OO3J_HuoYNokQM/s1600-h/leg-http-barchart.png&quot;&gt;&lt;img style=&quot;margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjeuLPXSksM7rkMKrMALXks8f_9pMkwyNRDHo0Px8XCHXa19Uum8cUp75mhVfHndP_mRV2o3ywe-_90uOK7T-6yGZ9DPop1BYQ4u1VIoIiFavMsoJ2OJlOhhqKAhW599OO3J_HuoYNokQM/s400/leg-http-barchart.png&quot; alt=&quot;&quot; id=&quot;BLOGGER_PHOTO_ID_5229197628906094674&quot; border=&quot;0&quot; /&gt;&lt;/a&gt;Times are changing. These days everything is http. Instead of doing a network install (does Ubuntu even support a network install?) we download the ISO (via http!), burn it and install that way. And, of course, we do updates over http as well. It is a pity we  don&#39;t have more data on http and it would have been nice to see where the change happened.&lt;br /&gt;&lt;br /&gt;I wrote the scripts to analyse the logs using &lt;a href=&quot;http://www.python.org&quot;&gt;python&lt;/a&gt;. I got to use &lt;a href=&quot;http://pyparsing.wikispaces.com&quot;&gt;pyparsing&lt;/a&gt; to parse up the Apache log file and the graphs were produced using &lt;a href=&quot;http://www.lorenzogil.com/projects/pycha&quot;&gt;pycha&lt;/a&gt;.</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/6185513352341687468/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/6185513352341687468' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/6185513352341687468'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/6185513352341687468'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/07/leg-download-stats.html' title='LEG download stats'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZpHF8jGYdM7fSEhuXy9iPhabVXjTNUFThkphMpm1gl-7d-nLHlT-ct4NldKjO_VfNP1kLGZEY79Z64-fvHDpryGc8w3ncUQJmKkLIxEFFnTDIMLJSVEC4WkBKHlSxhSSB11o2FV0-BRk/s72-c/leg-ftp-barchart.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-3255798406651531626</id><published>2008-06-06T12:08:00.003+02:00</published><updated>2008-06-06T12:32:52.088+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="pyparsing"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>More pyparsing and DHCP hosts</title><content type='html'>Since I wrote &lt;a href=&quot;http://craigbalfour.blogspot.com/2008/06/recursive-descent-parsers-and-pyparsing.html&quot;&gt;my original pyparsing post&lt;/a&gt; a few days ago, I&#39;ve done some more work on refining my ISC dhcpd.conf host parsing example program. I also received some &lt;a href=&quot;http://craigbalfour.blogspot.com/2008/06/recursive-descent-parsers-and-pyparsing.html?showComment=1212673200000#c7273537897369364249&quot;&gt;useful comments and suggests&lt;/a&gt; from Paul McGuire, the author the pyparsing module (thanks, Paul!), which I have also tried to incorporate.&lt;br /&gt;&lt;br /&gt;It&#39;s it&#39;s currently just a useless toy program but it is starting to look quite pretty.&lt;br /&gt;&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;#!/usr/bin/python&lt;br /&gt;&lt;br /&gt;from pyparsing import *&lt;br /&gt;&lt;br /&gt;# An few host entries from dhcpd.conf&lt;br /&gt;sample_data = &quot;&quot;&quot;&lt;br /&gt;&lt;br /&gt;# A host with dynamic DNS attributes&lt;br /&gt;host a.foo.bar {&lt;br /&gt;   ddns-hostname a;&lt;br /&gt;   ddns-domainname &quot;foo.bar&quot;;&lt;br /&gt;   hardware ethernet 00:11:22:33:44:55;&lt;br /&gt;   fixed-address 192.168.100.10, 192.168.200.50;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;# A simple multi-line host&lt;br /&gt;host b.foo.bar {&lt;br /&gt;   hardware ethernet 00:0f:12:34:56:78;&lt;br /&gt;   fixed-address 192.168.100.20;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;# A simple single-line host&lt;br /&gt;host c.foo.bar { hardware ethernet 00:0e:12:34:50:70; fixed-address 192.168.100.40; }&lt;br /&gt;&quot;&quot;&quot;&lt;br /&gt;&lt;br /&gt;digits = &quot;0123456789&quot;&lt;br /&gt;colon,semi,period,comma,lbrace,rbrace,quote = map(Literal,&#39;:;.,{}&quot;&#39;)&lt;br /&gt;number = Word(digits)&lt;br /&gt;hexint = Word(hexnums,exact=2)&lt;br /&gt;dnschars = Word(alphanums + &#39;-&#39;) # characters permissible in DNS names&lt;br /&gt;&lt;br /&gt;mac = Combine(hexint + (&quot;:&quot; + hexint) * 5)(&quot;mac_address&quot;)&lt;br /&gt;ip = Combine(number + period + number + period + number + period + number)&lt;br /&gt;ips = delimitedList(ip)(&quot;ip_addresses&quot;)&lt;br /&gt;hardware_ethernet  = Literal(&#39;hardware&#39;) + Literal(&#39;ethernet&#39;) + mac + semi&lt;br /&gt;hostname = dnschars&lt;br /&gt;domainname = dnschars + OneOrMore(&quot;.&quot; + dnschars)&lt;br /&gt;fqdn = Combine(hostname + period + domainname)(&quot;fqdn&quot;)&lt;br /&gt;&lt;br /&gt;fixed_address  = Literal(&#39;fixed-address&#39;) + ips + semi&lt;br /&gt;ddns_hostname = Literal(&#39;ddns-hostname&#39;) + hostname + semi&lt;br /&gt;ddns_domainname = Literal(&#39;ddns-domainname&#39;) + quote + domainname + quote + semi&lt;br /&gt;&lt;br /&gt;# Put the grammar together to define a host declaration&lt;br /&gt;host = Literal(&#39;host&#39;) + fqdn + lbrace + Optional(ddns_hostname) + Optional(ddns_domainname) + Optional(hardware_ethernet) + Optional(fixed_address) + rbrace&lt;br /&gt;&lt;br /&gt;results = host.scanString(sample_data)&lt;br /&gt;&lt;br /&gt;for result in results:&lt;br /&gt; print result[0].fqdn, result[0].mac_address , result[0].ip_addresses&lt;br /&gt;&lt;br /&gt;file.close()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The output is still the same as that of the original program. &lt;br /&gt;&lt;br /&gt;If you want it to read your sample data from a file, just change the following line:&lt;br /&gt;&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;results = host.scanString(sample_data)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;... to:&lt;br /&gt;&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;filename = &quot;/etc/dhcp3/dhcpd.conf&quot;&lt;br /&gt;file = open(filename, &quot;rb&quot;)&lt;br /&gt;results = host.scanString(&quot;&quot;.join(file.readlines()))&lt;br /&gt;&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/3255798406651531626/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/3255798406651531626' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/3255798406651531626'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/3255798406651531626'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/06/more-pyparsing-and-dhcp-hosts.html' title='More pyparsing and DHCP hosts'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-3108281715199590992</id><published>2008-06-03T09:59:00.010+02:00</published><updated>2008-06-06T12:33:12.785+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="pyparsing"/><category scheme="http://www.blogger.com/atom/ns#" term="Python"/><title type='text'>Recursive Descent Parsers and pyparsing</title><content type='html'>Yesterday while browsing the table of contents of the &lt;a href=&quot;http://blog.doughellmann.com/2008/05/python-magazine-for-may-2008.html&quot;&gt;May 2008 issue of Python Magazine&lt;/a&gt; I came across a reference to the &lt;a href=&quot;http://pyparsing.wikispaces.com/&quot;&gt;pyparsing&lt;/a&gt; module - a python module for writing &lt;a href=&quot;http://en.wikipedia.org/wiki/Recursive_descent_parser&quot;&gt;recursive descent parsers&lt;/a&gt; using familiar python grammar.&lt;br /&gt;&lt;br /&gt;O&#39;Reilly&#39;s Python DevCenter has an excellent introduction to using this module entitled &lt;a href=&quot;http://www.onlamp.com/pub/a/python/2006/01/26/pyparsing.html&quot;&gt;Building Recursive Descent Parsers with Python&lt;/a&gt;. Well worth a read.&lt;br /&gt;&lt;br /&gt;It just so happens that I have a number of projects which are stalled because writing code to parse complexly structured data is not my strong point. I enjoy parsing up text line by line as much as the next guy but this recursive stuff I find tedious.&lt;br /&gt;&lt;br /&gt;The &lt;a href=&quot;http://www.daemon-systems.org/man/dhcpd.conf.5.html&quot;&gt;ISC DHCP configuration file&lt;/a&gt; is, in my opinion, a good example of parsing complexity.  It&#39;s configuration directives can contain many optional directives, can be nested, and can be all on a single line or broken up move multiple lines. Writing the parser using pyparsing makes this much simpler.&lt;br /&gt;&lt;br /&gt;Here is a simple example of using pyparsing to parse a few host definitions which while simple is quite flexible.&lt;br /&gt;&lt;br /&gt;&lt;pre name=&quot;code&quot; class=&quot;python&quot;&gt;&lt;br /&gt;#!/usr/bin/python&lt;br /&gt;from pyparsing import *&lt;br /&gt;&lt;br /&gt;# Define the grammar we will use&lt;br /&gt;digits = &quot;0123456789&quot;&lt;br /&gt;colon = Literal(&#39;:&#39;)&lt;br /&gt;semi = Literal(&#39;;&#39;)&lt;br /&gt;period = Literal(&#39;.&#39;)&lt;br /&gt;comma = Literal(&#39;,&#39;)&lt;br /&gt;lparen = Literal(&#39;{&#39;)&lt;br /&gt;rparen = Literal(&#39;}&#39;)&lt;br /&gt;number = Word(digits)&lt;br /&gt;hexint = Word(hexnums,exact=2)&lt;br /&gt;text = Word(alphas)&lt;br /&gt;&lt;br /&gt;# Define host configuration specific grammar&lt;br /&gt;host_keyword = Literal(&#39;host&#39;)&lt;br /&gt;hardware_keyword = Literal(&#39;hardware&#39;)&lt;br /&gt;ethernet_keyword = Literal(&#39;ethernet&#39;)&lt;br /&gt;address_keyword = Literal(&#39;fixed-address&#39;)&lt;br /&gt;mac = Combine(hexint + colon + hexint + colon + hexint + colon + hexint + colon + hexint + colon + hexint).setResultsName(&quot;mac_address&quot;)&lt;br /&gt;ip = Combine(number + period + number + period + number + period + number)&lt;br /&gt;ips = delimitedList(ip).setResultsName(&quot;ip_addresses&quot;)&lt;br /&gt;hostname = Combine(text + period + text + period + text).setResultsName(&quot;hostname&quot;)&lt;br /&gt;ethernet_statement = hardware_keyword + ethernet_keyword + mac + semi&lt;br /&gt;&lt;br /&gt;ipaddress_statement = address_keyword + ips + semi&lt;br /&gt;x = host_keyword + hostname + lparen + Optional(ethernet_statement) + Optional(ipaddress_statement) + rparen&lt;br /&gt;&lt;br /&gt;# Here is some sample data for us to parse&lt;br /&gt;&lt;br /&gt;host_declaration = &quot;&quot;&quot;&lt;br /&gt;host a.foo.bar {&lt;br /&gt;    hardware ethernet 00:11:22:33:44:55;&lt;br /&gt;    fixed-address 192.168.100.10, 192.168.200.50;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;host b.foo.bar {&lt;br /&gt;    hardware ethernet 00:0f:12:34:56:78;&lt;br /&gt;    fixed-address 192.168.100.20;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;host c.foo.bar { hardware ethernet 00:0e:12:34:50:70; fixed-address 192.168.100.40; }&lt;br /&gt;&quot;&quot;&quot;&lt;br /&gt;&lt;br /&gt;# Do the parsing&lt;br /&gt;&lt;br /&gt;results = x.scanString(host_declaration)&lt;br /&gt;&lt;br /&gt;# Print out the stuff we&#39;re interested&lt;br /&gt;&lt;br /&gt;for result in results:&lt;br /&gt;    print result[0].hostname, result[0].mac_address , result[0].ip_addresses&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The output looks like this:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;a.foo.bar 00:11:22:33:44:55 [&#39;192.168.100.10&#39;, &#39;192.168.200.50&#39;]&lt;br /&gt;b.foo.bar 00:0f:12:34:56:78 [&#39;192.168.100.20&#39;]&lt;br /&gt;c.foo.bar 00:0e:12:34:50:70 [&#39;192.168.100.40&#39;]&lt;br /&gt;&lt;/code&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/3108281715199590992/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/3108281715199590992' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/3108281715199590992'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/3108281715199590992'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/06/recursive-descent-parsers-and-pyparsing.html' title='Recursive Descent Parsers and pyparsing'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-1496454028754543656</id><published>2008-05-23T14:17:00.005+02:00</published><updated>2008-05-23T15:34:49.309+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="OpenLDAP"/><title type='text'>Ubuntu Hardy and OpenLDAP 2.4</title><content type='html'>By the looks of it Ubuntu took a giant leap of faith with OpenLDAP in Hardy Heron! They upgraded from using a reasonably stable OpenLDAP 2.3.35 in Gutsy to 2.4.7 with lots of replication problems in Hardy.&lt;br /&gt;&lt;br /&gt;Why did they do it?! According to OpenLDAP.org the 2.3 branch is the stable release branch and 2.4 is the general release branch. OpenLDAP 2.4 seems currently be plagued by replication stability issues. Surely it makes sense to run the stable branch?!&lt;br /&gt;&lt;br /&gt;Last night I made the mistake of upgrading our syncrepl master LDAP server from Gutsy to Hardy and now it segfaults regularly. The slave has been running Hardy for some time now without any problems, so I mistakenly thought it was safe.&lt;br /&gt;&lt;br /&gt;I first thought the problems were Berkeley DB back-end data corruption issues - of which we have had many in the past - but repairing the databases, and even completely rebuilding them from LDIF backup has made no significant different to the (in)stability.&lt;br /&gt;&lt;br /&gt;The question now is: Do I upgrade to 2.4.9 (from source since there doesn&#39;t seem to be any debs around yet) which by the looks of the &lt;a href=&quot;http://www.openldap.org/lists/openldap-announce/200805/msg00000.html&quot;&gt;release announcement&lt;/a&gt; has plenty of replication fixes, or should I try and go back to 2.3.35 from Gutsy?  Decisions, decisions!&lt;br /&gt;&lt;br /&gt;Update: I&#39;ve decided to go back to OpenLDAP 2.3.35 from Gutsy. By the looks of &lt;a href=&quot;http://mmartinsoftware.blogspot.com/2008/05/openldap-249-multi-master-replication.html&quot;&gt;this blog post&lt;/a&gt;, 2.4.9&#39;s replication is looking much better and perhaps ready for the big-time. When I get a chance, I&#39;ll give it a go in the lab this time :).</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/1496454028754543656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/1496454028754543656' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/1496454028754543656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/1496454028754543656'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/05/ubuntu-hardy-and-openldap-24.html' title='Ubuntu Hardy and OpenLDAP 2.4'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-1010737200251701685</id><published>2008-05-23T12:42:00.004+02:00</published><updated>2008-05-23T13:24:57.303+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Exim"/><title type='text'>exiqsumm: Getting to know your (Exim) mail queues</title><content type='html'>Running &lt;span style=&quot;font-family: courier new;&quot;&gt;exiqsumm&lt;/span&gt; a few times a day via cron is an excellent way to get a sense of what is normal for your mail queues and will help you pick up all sorts of problems.&lt;br /&gt;&lt;br /&gt;For example:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Lots of mail waiting to be delivered to your local domain may indicate a local delivery problem like a broken local delivery agent (perhaps you just did a quick distribution update / upgrade and something important broke?), your mail volume has filled up, or some important, high volume user has filled up his mailbox and now his mail is just sitting on the queue?&lt;/li&gt;&lt;li&gt;Lots of mail waiting to be delivered to domain example.com? Perhaps example.com&#39;s mail server is down, or a network problem has made them unreachable?&lt;/li&gt;&lt;li&gt;Lots of mail waiting to go out to lots of different, perhaps suspiciously named, domains could indicate that your mail server is being used to relay spam. &lt;/li&gt;&lt;/ul&gt; Here&#39;s how you use it: &lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;/usr/local/sbin/exim -bp | /usr/local/sbin/exiqsumm &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Exiqsumm will give you a nice tabular summary of what is sitting in your mail queue on a per domain basis.&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;Count  Volume  Oldest  Newest  Domain&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;-----  ------  ------  ------  ------&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3276     20h     20h  0575.info&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    2048     47h     47h  1st4diaries.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3891     33h     33h  able-courier-inc.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3481     41h     41h  adelphia.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3481     47h     47h  alliance-lawyers.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3993     18h     18h  alvar.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    2662     37h     37h  astound.net&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    2252     26h     26h  bcinternet.net&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    4812     16h     16h  belmarparkdental.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    5    8192      4d      3h  blitzbeat.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    2150     47h     47h  boardley.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    1843     47h     47h  bochao.com.cn&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    2355     32h     32h  brace-ingenierie.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3379     13h     13h  brkoday.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    1    3379      3h      3h  broste.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;    5    9932      5d     16h  centrapoint.com&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;...&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;---------------------------------------------------------------&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: courier new;&quot;&gt;  109   637KB      5d      2h  TOTAL&lt;/span&gt;</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/1010737200251701685/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/1010737200251701685' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/1010737200251701685'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/1010737200251701685'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/05/exiqsumm-getting-to-know-your-exim-mail.html' title='exiqsumm: Getting to know your (Exim) mail queues'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-8817783622604962602</id><published>2008-05-22T17:20:00.003+02:00</published><updated>2008-05-23T10:51:20.848+02:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="OpenLDAP"/><title type='text'>Some nice advanced topic OpenLDAP articles</title><content type='html'>I&#39;m always on the lookout for information on advanced OpenLDAP topics, something I&#39;ve found to be in short supply on the internet.&lt;br /&gt;&lt;br /&gt;By advanced I mean documentation, tutorials and howto&#39;s that go beyond just setting up a simple standalone directory or an address book and gets into doing things with all of the fancy backends and overlays available in OpenLDAP these days.&lt;br /&gt;&lt;br /&gt;I recently came across the &lt;a href=&quot;http://www.connexitor.com/forums/viewforum.php?f=6&amp;amp;sid=615857213c03c452049b777032d76a5c&quot;&gt;Symas forums&lt;/a&gt; which have a few nice articles on advanced OpenLDAP topics:&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Password policies (&lt;a href=&quot;http://www.connexitor.com/forums/viewtopic.php?f=6&amp;amp;t=25&amp;amp;sid=19532b8828570bccc5772bb76aecee31&quot;&gt;Managing Password Policies in the Directory&lt;/a&gt;) which describes how to add password policies such as expiry, aging, minimum length, history, etc which are features considered standard in Novell E-directory and Microsoft Active Directory accounts but are lacking in the basic OpenLDAP authentication directory configuration. Unfortunately, it falls short of giving any information on whether clients such as nss_ldap and pam_ldap support this, so I will need to test this in a lab sometime.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;The &lt;a href=&quot;http://www.connexitor.com/forums/viewtopic.php?f=6&amp;amp;t=20&amp;amp;sid=19532b8828570bccc5772bb76aecee31&quot;&gt;Transluscent Overlay&lt;/a&gt; sounds really useful. One use I have for this feature is to tack the attributes I need for unix authentication (e.g. uid, gid, etc) to our organisations main directory (which I have no control over) so that I my users can use the same password to log into my servers as they use to log into the organisations systems. &lt;/li&gt;&lt;li&gt;There is also some stuff about replication, as well as the referential integrity and attribute uniqueness overlays. &lt;/li&gt;&lt;/ul&gt;More of this kind of article please!</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/8817783622604962602/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/8817783622604962602' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/8817783622604962602'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/8817783622604962602'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/05/im-always-on-lookout-for-information-on.html' title='Some nice advanced topic OpenLDAP articles'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5304945066026788056.post-351793338405897754</id><published>2008-05-22T15:01:00.002+02:00</published><updated>2008-05-22T15:31:27.485+02:00</updated><title type='text'>A new blog is born</title><content type='html'>I&#39;ve decided to start a new blog.&lt;br /&gt;&lt;br /&gt;I know, &lt;a href=&quot;http://balfouring.blogspot.com&quot;&gt;my &lt;/a&gt;&lt;a href=&quot;http://balfouring.blogspot.com&quot;&gt;family blog&lt;/a&gt; has been languishing badly but I&#39;ve needed a place to post the details of my more technical exploits for a while now - even if only so I can refer back to the details again later  - and that blog just hasn&#39;t seemed like the right place.&lt;br /&gt;&lt;br /&gt;I&#39;ve needed a new place and this is it. Let&#39;s see how this new blog works out.</content><link rel='replies' type='application/atom+xml' href='http://craigbalfour.blogspot.com/feeds/351793338405897754/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/5304945066026788056/351793338405897754' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/351793338405897754'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5304945066026788056/posts/default/351793338405897754'/><link rel='alternate' type='text/html' href='http://craigbalfour.blogspot.com/2008/05/new-blog.html' title='A new blog is born'/><author><name>Craig</name><uri>http://www.blogger.com/profile/17415022803530135325</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='25' height='32' src='//blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgK76CQvyvgWndhCRie5gTMdZlmnJfsoot4sT9eBTM-0OVNdrjOiiFmMYLxmqkREqsTsja8q0DlaIQho9Xa_x-e1AWkrgzY-JfzQxu0mwQ_zcRWgqEKqTzeD-E68gPMGzA/s220/craig.jpg'/></author><thr:total>0</thr:total></entry></feed>