<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	>

<channel>
	<title>Mike Renfro's Blog</title>
	<atom:link href="http://blogs.cae.tntech.edu/mwr/feed/" rel="self" type="application/rss+xml" />
	<link>http://blogs.cae.tntech.edu/mwr</link>
	<description>A partial repository of whatever comes to mind</description>
	<pubDate>Mon, 25 Aug 2008 22:17:05 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.5.1</generator>
	<language>en</language>
			<item>
		<title>DIY Planner style TTU Campus Map</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/08/25/diy-planner-style-ttu-campus-map/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/08/25/diy-planner-style-ttu-campus-map/#comments</comments>
		<pubDate>Mon, 25 Aug 2008 22:17:05 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/?p=91</guid>
		<description><![CDATA[A user over at diyplanner.com posted a simple campus map and course information template for college students. I thought it might be useful to make one for TTU.
So here you go:

PDF version, ready to be printed onto two side-by-side pages. Not sure why the fonts didn&#8217;t completely translate over.
OpenOffice.org version, ready to be edited.

]]></description>
			<content:encoded><![CDATA[<p>A user over at <a href="http://diyplanner.com/">diyplanner.com</a> posted <a href="http://diyplanner.com/node/5937">a simple campus map and course information template</a> for college students. I thought it might be useful to make one for TTU.</p>
<p>So here you go:</p>
<ul>
<li><a href='http://blogs.cae.tntech.edu/mwr/files/2008/08/classes_ooo.pdf'>PDF version</a>, ready to be printed onto two side-by-side pages. Not sure why the fonts didn&#8217;t completely translate over.</li>
<li><a href='http://blogs.cae.tntech.edu/mwr/files/2008/08/classes_ooo.zip'>OpenOffice.org version</a>, ready to be edited.</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/08/25/diy-planner-style-ttu-campus-map/feed/</wfw:commentRss>
		</item>
		<item>
		<title>If The Title of This Post Contains the Word &#8220;Tennessee&#8221;, Will It Attract Human Spammers?</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/08/11/if-the-title-of-this-post-contains-the-word-tennessee-will-it-attract-human-spammers/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/08/11/if-the-title-of-this-post-contains-the-word-tennessee-will-it-attract-human-spammers/#comments</comments>
		<pubDate>Mon, 11 Aug 2008 14:12:22 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/?p=90</guid>
		<description><![CDATA[As noted before, I do love Spam Karma 2. 4000+ spams eaten over the last 18 months or so, and it&#8217;s nearly perfect as far as I can tell.
But it didn&#8217;t catch a couple of&#8230; terse&#8230; commenters on this post about a presentation I was about to give. One commenter seemed rather irritated, and posted [...]]]></description>
			<content:encoded><![CDATA[<p>As noted before, <a href="http://blogs.cae.tntech.edu/mwr/2007/06/23/i-love-spam-karma-2/">I do love Spam Karma 2</a>. 4000+ spams eaten over the last 18 months or so, and it&#8217;s nearly perfect as far as I can tell.</p>
<p>But it didn&#8217;t catch a couple of&#8230; terse&#8230; commenters on <a href="http://blogs.cae.tntech.edu/mwr/2008/04/22/giving-a-presentation-at-the-tennessee-higher-education-it-symposium/">this post</a> about a presentation I was about to give. One commenter seemed rather irritated, and posted a malformed link to some addition recovery place or another.  A second commenter appeared to be much friendlier, and posted a few words mildly relevant to the presentation, but also added a link to another treatment center. And it&#8217;s just happening on this <strong>one</strong> post, as far as I can tell.</p>
<p>And this isn&#8217;t the garden-variety spam I&#8217;m used to seeing in Spam Karma 2&#8217;s reports. Which means it&#8217;s not getting caught with their Javascript test, their &#8220;Flash Gordon was here&#8221; test (comment posted just a few seconds after page load), etc. The comments contain one link, nothing formatted with BBCode, no unformatted links, and there are complete sentences attached to them that are just barely related to the post content.</p>
<p>Of all the posts I&#8217;ve got here, and all the opportunity for spam it provides, why this post, and why these spammers? The only thing the post and the spams have in common is the word &#8220;Tennessee&#8221;. Spammer 1 tried to link to an addiction recovery site with the word Tennessee in its URL. Spammer 2 successfully linked to a treatment center with the word Tennessee in its URL.</p>
<p>So I wonder, did I include the word &#8220;Tennessee&#8221; in this post enough to attract these folks&#8217; attention? Will they post more spam here? Because if they do, what Spam Karma 2 doesn&#8217;t catch, I will. Word of advice, guys. I don&#8217;t get an enormous volume of comments here. In fact, I get few enough to where I&#8217;m normally able to look at them within minutes of their arrival. If they&#8217;re spam, they&#8217;ll get deleted. If you&#8217;re getting paid by the hour, I guess it doesn&#8217;t matter. If you&#8217;re getting paid by number of valid links left after some period of time, you&#8217;re better off spamming elsewhere.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/08/11/if-the-title-of-this-post-contains-the-word-tennessee-will-it-attract-human-spammers/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Where&#8217;s MarcoPolo for Windows?</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/25/wheres-marcopolo-for-windows/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/25/wheres-marcopolo-for-windows/#comments</comments>
		<pubDate>Sat, 26 Jul 2008 04:26:40 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/?p=85</guid>
		<description><![CDATA[MarcoPolo, &#8220;context-aware computing for OS X&#8221; appeals to all three great virtues of the programmer: Laziness, Impatience, and Hubris. It makes me want a Mac notebook. Unfortunately, Pro/E, ANSYS, and other necessary tools for my regular work would mean I&#8217;d end up dual-booting the Mac all the time. And I&#8217;d miss my port replicator and [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.symonds.id.au/marcopolo/">MarcoPolo</a>, &#8220;context-aware computing for OS X&#8221; appeals to all three great virtues of the programmer: Laziness, Impatience, and Hubris. It makes me want a Mac notebook. Unfortunately, Pro/E, ANSYS, and other necessary tools for my regular work would mean I&#8217;d end up dual-booting the Mac all the time. And I&#8217;d miss my port replicator and its enabling me to have one connection to the monitor, keyboard, mouse, network, etc.</p>
<p>So, why not build a MarcoPolo for Windows? I&#8217;ve not yet found one, but I&#8217;d be more than happy to be proven wrong there. In the meantime, I&#8217;m working on a Python/Windows proof-of-concept that could be the groundwork for a Windows analogue to MarcoPolo.<br />
<span id="more-85"></span></p>
<pre name="code" class="py">

# Idea for Win32 version of MarcoPolo (Context-aware computing for Mac OS X)

# Contexts
contexts = {
    &#039;Home&#039;: 1,
    &#039;Work&#039;: 1,
    &#039;pre-Noon&#039;: 1,
    &#039;post-Noon&#039;: 1,
    &#039;Unknown&#039;: 1,
}

import wmi, pybonjour, time, IP4Range
# wmi: http://tgolden.sc.sabren.com/python/wmi.html
# pybonjour: http://o2s.csail.mit.edu/o2s-wiki/pybonjour
# IP4Range: http://code.activestate.com/recipes/466298/

def main():
    c=wmi.WMI()
    # Evidence sources via WMI (direct from MarcoPolo features, some
    # sources may not be available in Win32)

    # Current audio output device (headphones/speakers) -- may not be
    # discoverable on XP and earlier.

    # Discoverable Bluetooth devices -- can find a Bluetooth adapter via
    # USB, but not a connected end-user device

    # Advertised Bonjour (Zeroconf) services -- &#039;browse_and_resolve.py
    # _daap._tcp&#039; finds a remote iTunes share, but no idea how to look for
    # random services.

    # Attached FireWire devices -- no FireWire-capable computers on hand

    # Assigned IP addresses
    ipList=[]
    for adapter in c.Win32_NetworkAdapterConfiguration(IPEnabled=&quot;True&quot;):
        ipList.append(adapter.IPAddress[0])

    # Ambient light level — may not be any standard sensors available to
    # Windows

    # Attached monitors
    monitorList=[]
    for monitor in c.Win32_DesktopMonitor(Availability=3):
        monitorList.append(monitor.Name)

    # Active network links
    networkAdapterList=[]
    for adapter in c.Win32_NetworkAdapter(NetConnectionStatus=2):
        networkAdapterList.append(adapter.Description)

    # Power source (power adapter/battery) — need to test on laptop

    # Running Applications
    applicationList=[]
    for process in c.Win32_Process():
        applicationList.append(process.Name)

    # Time of day
    timeOfDay=time.strftime(&quot;%H:%M&quot;)

    # Attached USB devices
    usbDeviceList=[]
    for device in c.Win32_USBControllerDevice():
        usbDeviceList.append(device.Dependent.Description)

    # Visible WiFi networks — need to test on laptop

    # Rules (type, criteria, context, confidence)
    ruleList = [
        (&#039;usb&#039;, &#039;Brother HL-5140 series&#039;, &#039;Home&#039;, 0.80),
        (&#039;usb&#039;, &#039;Visioneer OneTouch 7300&#039;, &#039;Home&#039;, 0.99),
        (&#039;monitor&#039;, &#039;Plug and Play Monitor&#039;, &#039;Home&#039;, 0.60),
        (&#039;ip&#039;, &#039;192.168.254.4&#039;, &#039;Home&#039;, 0.50),
        (&#039;ip&#039;, &#039;149.149.254.0/24&#039;, &#039;Work&#039;, 0.99),
        (&#039;ssid&#039;, &#039;mike-and-carolyn&#039;, &#039;Home&#039;, 0.99),
        (&#039;time&#039;, [&#039;00:00&#039;, &#039;11:59&#039;], &#039;pre-Noon&#039;, 0.99),
        (&#039;time&#039;, [&#039;12:00&#039;, &#039;23:59&#039;], &#039;post-Noon&#039;, 0.99),
        ]

    # Decision
    for rule in ruleList:
        itemFound = -1
        (ruleType, ruleItem, ruleContext, ruleStrength) = rule
        if ruleType==&#039;ip&#039;:
            ipRange=IP4Range.IP4Range(ruleItem)
            for ip in ipList:
                currentIP=IP4Range.IP4Range(ip.encode(&#039;ascii&#039;))
                if currentIP.issubset(ipRange):
                    itemFound = 1

        elif ruleType==&#039;monitor&#039;:
            try:
                itemFound = monitorList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;adapter&#039;:
            try:
                itemFound = networkAdapterList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;application&#039;:
            try:
                itemFound = applicationList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;usb&#039;:
            try:
                itemFound = usbDeviceList.index(ruleItem)
            except ValueError:
                pass
        elif ruleType==&#039;time&#039;:
            if cmp(timeOfDay,ruleItem[0])&gt;=0 and cmp(timeOfDay,ruleItem[1])&lt;=0:
                itemFound=1
         else:
            print &quot;Unknown rule type %s&quot; % (ruleType)
            continue

        if itemFound!=-1:
            contexts[ruleContext]=contexts[ruleContext]*(1-ruleStrength)

    myLocation=&#039;Unknown&#039;
    myConfidence=1-contexts[&#039;Unknown&#039;]
    for context in contexts:
        if contexts[context]!=1:
            if (1-contexts[context])&gt;myConfidence:
                myLocation=context
                myConfidence = 1-contexts[context]

    print &quot;Your location: %s (confidence %f)&quot; % (myLocation, myConfidence)
    print

if __name__ == &quot;__main__&quot;:
    main()
</pre>
<p>I know my Python is mediocre at best. But this does work so far in my limited testing (&#8221;Unknown rule type ssid, Your location: Home (confidence 0.999600)&#8221;). Thanks to David Symonds for his assistance in <a href="http://groups.google.com/group/marcopolo-discuss/browse_thread/thread/c4c4e7f6521af282/396bbcff92029b7b">this thread</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/25/wheres-marcopolo-for-windows/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Upgraded to WPMU 2.5.1</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/23/upgraded-to-wpmu-251/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/23/upgraded-to-wpmu-251/#comments</comments>
		<pubDate>Thu, 24 Jul 2008 04:08:23 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/?p=84</guid>
		<description><![CDATA[Joy. Upgraded the site to WPMU 2.5.1. Took a while to reconstruct my restrictions for new users and blog creation, and I&#8217;m switching out some syntax-highlighting plugins, too. We&#8217;re nearly ready to unleash this on a horde of unsuspecting graduate students.
Code Example:


/**
 * This holds the version number in a separate file so we can [...]]]></description>
			<content:encoded><![CDATA[<p>Joy. Upgraded the site to WPMU 2.5.1. Took a while to reconstruct my restrictions for new users and blog creation, and I&#8217;m switching out some syntax-highlighting plugins, too. We&#8217;re nearly ready to unleash this on a horde of unsuspecting graduate students.</p>
<p>Code Example:</p>
<pre name="code" class="php">

/**
 * This holds the version number in a separate file so we can bump it without cluttering the SVN
 */

/**
 * The WordPress version string
 *
 * @global string $wp_version
 */
$wp_version = &#039;2.5.1&#039;;

/**
 * Holds the WordPress DB revision, increments when changes are made to the WordPress DB scheme
 * changes.
 *
 * @global int $wp_db_version
 */
$wp_db_version = 7796;

$wpmu_version = &#039;1.5.1&#039;;
</pre>
<p>Math Example:<br />
<img src='/latexrender/pictures/f46519f38940376c83f844b8bf1fe46e.gif' title='2+e^{i\pi}=1' alt='2+e^{i\pi}=1' align=absmiddle></p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/23/upgraded-to-wpmu-251/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Stupid Puppet Trick: Identifying Groups of Hosts</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/13/stupid-puppet-trick-identifying-groups-of-hosts/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/13/stupid-puppet-trick-identifying-groups-of-hosts/#comments</comments>
		<pubDate>Mon, 14 Jul 2008 03:08:21 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[infrastructures]]></category>

		<category><![CDATA[puppet]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/07/13/stupid-puppet-trick-identifying-groups-of-hosts/</guid>
		<description><![CDATA[My Ruby skills are practically non-existant, but I&#8217;ve managed to put together a relatively readable custom fact for identifying my Torque queues by a node&#8217;s hostname. Behold, HostgroupFact! Now I can factor out my hosts.equiv files back to a parent class, rather than duplicating the same file specifications on a per-queue basis.

class cluster-host inherits public-host [...]]]></description>
			<content:encoded><![CDATA[<p>My Ruby skills are practically non-existant, but I&#8217;ve managed to put together a relatively readable custom fact for identifying my Torque queues by a node&#8217;s hostname. Behold, <a href="http://reductivelabs.com/trac/puppet/wiki/Recipes/HostgroupFact">HostgroupFact</a>! Now I can factor out my hosts.equiv files back to a parent class, rather than duplicating the same file specifications on a per-queue basis.</p>
<pre>
class cluster-host inherits public-host {
    # ...
    file { "/etc/hosts.equiv":
        source  =&gt; "puppet:///files/apps/rsh-server/hosts.equiv.$hostgroup",
        owner   =&gt; root,
        group   =&gt; root,
        mode    =&gt; 644,
        require =&gt; Package[rsh-server],
    }
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/13/stupid-puppet-trick-identifying-groups-of-hosts/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Stupid Puppet Trick: Poor Man&#8217;s Undo</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/12/stupid-puppet-trick-poor-mans-undo/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/12/stupid-puppet-trick-poor-mans-undo/#comments</comments>
		<pubDate>Sun, 13 Jul 2008 01:16:07 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[infrastructures]]></category>

		<category><![CDATA[puppet]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/07/12/stupid-puppet-trick-poor-mans-undo/</guid>
		<description><![CDATA[If I apply a set of classes to a puppet client, I may need to roll back those classes&#8217; changes later. Granted, I could just edit out those classes, reformat the system, and rebuild it from scratch, but there may be times when I want to undo my changes in a more granular fashion. So [...]]]></description>
			<content:encoded><![CDATA[<p>If I apply a set of classes to a puppet client, I may need to roll back those classes&#8217; changes later. Granted, I could just edit out those classes, reformat the system, and rebuild it from scratch, but there may be times when I want to undo my changes in a more granular fashion. So here&#8217;s my latest revelation (which is undoubtedly documented elsewhere already, but I didn&#8217;t find anything sufficiently simple last time I looked).</p>
<p>Along the lines of <a href="http://reductivelabs.com/trac/puppet/wiki/PuppetBestPractice">Puppet Best Practice 2.0</a>, I&#8217;m trying to compartmentalize all my building blocks into separate modules, even the really simple ones that just install a package or two. So here&#8217;s the contents of a trivial hello module for Debian:</p>
<pre>
# /etc/puppet/modules/hello/manifests/init.pp
class hello {
  package { "hello":
    ensure =&gt; latest;
  }
}
</pre>
<p>Nothing complicated here: install this package via the default package provider. Add an <code>include hello</code> to my node&#8217;s manifest, and the hello package gets installed.</p>
<p>Now for the &#8220;undo&#8221; class:</p>
<pre>
# /etc/puppet/modules/hello/manifests/disabled.pp
class hello::disabled inherits hello {
  Package["hello"] {
    ensure =&gt; purged
  }
}
</pre>
<p>And this is neat. Properly written, the disabling class can undo all the changes made by the parent class. Modify my node&#8217;s manifest from <code>include hello</code> to <code>include hello::disabled</code> and I can switch that class on and off like, well, a switch.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/12/stupid-puppet-trick-poor-mans-undo/feed/</wfw:commentRss>
		</item>
		<item>
		<title>I Killed the Mail Server Today</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/09/i-killed-the-mail-server-today/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/09/i-killed-the-mail-server-today/#comments</comments>
		<pubDate>Thu, 10 Jul 2008 03:00:57 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Debian]]></category>

		<category><![CDATA[Stupid!]]></category>

		<category><![CDATA[Xen]]></category>

		<category><![CDATA[infrastructures]]></category>

		<category><![CDATA[puppet]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/07/09/i-killed-the-mail-server-today-2/</guid>
		<description><![CDATA[
It all started so simply: I was going to set up a little Xen instance to be my next cluster submit host, and needed a spare address for it:

I started setting up an instance for ch208i.cae.tntech.edu, since it was no longer on the Xen host like it was several months ago. Crap, the reason it&#8217;s [...]]]></description>
			<content:encoded><![CDATA[<div><a href="http://icanhascheezburger.com/2008/07/07/funny-pictures-i-have-the-dumb/"><img class="mine_1410833" src="http://icanhascheezburger.wordpress.com/files/2008/07/funny-pictures-cat-cannot-brain-today.jpg" alt="cat" /></a></div>
<p>It all started so simply: I was going to set up a little Xen instance to be my next cluster submit host, and needed a spare address for it:</p>
<ol>
<li>I started setting up an instance for ch208i.cae.tntech.edu, since it was no longer on the Xen host like it was several months ago. Crap, the reason it&#8217;s no longer on the Xen instance is because I moved it to its own dedicated hardware &#8212; it&#8217;s still my main ftp/mirror server. Ctrl-C that one.</li>
<li>Hmm, what&#8217;s available from old Xen instances? mail2.cae.tntech.edu.cfg from when I was testing out a new mail server setup last fall &#8212; doesn&#8217;t ping, doesn&#8217;t show up in <code>xm list</code>, no problem.
<pre>xen-create-image --hostname=mail2.cae.tntech.edu --ip=149.149.254.23 \
    --gateway=149.149.254.4 --netmask=255.255.255.0 --size=10Gb --memory=256Mb \
    --swap=1Gb --debootstrap --force</pre>
<p>A few minutes later, my instance is debootstrapped and ready to go.</li>
<li>Oh, crap. Why am I getting an error on <code>xm create</code> that says my LVM is already in use on a domU somewhere?</li>
<li>Further crap. Looking in <code>/etc/xen/mail.cae.tntech.edu.cfg</code> for the production mail server, it apparently uses the old mail2.cae.tntech.edu LVMs. Wonderful. <code>ssh mail</code>? It works since sshd was already memory-resident, but <code>/root/.profile</code> doesn&#8217;t exist. And neither does much of anything else.</li>
<li>Great. I&#8217;ve just killed the mail server. Off to the Amanda server to do a quick restore of its data. What? I never put mail.cae.tntech.edu into the backup list? Not normally the end of the world, since the mail stores are held accessed over NFS from the main file server, but what about my dovecot and postfix configurations?</li>
<li>Oh, well. Time to see how good my puppet manifests are for the mail server.</li>
</ol>
<p>Not too bad, as it turns out. Total downtime was only a couple hours, including having to redo the postfix and dovecot configurations (which were then copied off to the puppetmaster). I still have a few more things to fix, but mail delivery is up, and imap is running. TLS support for my sending mail from home isn&#8217;t up yet, but it&#8217;ll be fixed shortly.</p>
<p>I still need to fix that submit host, though. Next time, I think I&#8217;ll use an IP address reserved for my office.</p>
<p><strong>Update:</strong> after getting a partial TLS/SASL setup going late Wednesday night, I went to sleep without realizing I&#8217;d killed mail delivery again. Finally got it straightened out Thursday morning.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/09/i-killed-the-mail-server-today/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Better Inventory Management Through Python</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/#comments</comments>
		<pubDate>Tue, 01 Jul 2008 21:58:50 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/</guid>
		<description><![CDATA[One good thing about the switch from the legacy administrative computing system to the new Banner setup is now I can more easily get an unformatted table of my department&#8217;s equipment inventory instead of a printed report. This matters to me for a few reasons:

The default inventory reports always come out sorted by inventory tag. [...]]]></description>
			<content:encoded><![CDATA[<p>One good thing about the switch from the legacy administrative computing system to the new Banner setup is now I can more easily get an unformatted table of my department&#8217;s equipment inventory instead of a printed report. This matters to me for a few reasons:</p>
<ul>
<li>The default inventory reports always come out sorted by inventory tag. I have no idea why. If you have anything more than a handful of items to inventory, you would sort them by room, just like you&#8217;d identify them when walking around. Since I have somewhere around 100 items to inventory across 6-7 buildings, it&#8217;s a pain and rather error-prone to manually mark the ones in a particular building.</li>
<li>Even when I got a report sorted by location, that doesn&#8217;t help a great deal with items that I only see during inventory time, or during a random audit. My memory just isn&#8217;t that good, and occasionally an item will move from one room to another without my knowledge. Photos of the items would help greatly in finding them, but the default reports don&#8217;t include any.</li>
</ul>
<p>So this year, within a few hours of first asking for it, I got a nice CSV file of our inventory from the Business Office (thanks, Matt). It included lines like<br />
<code><br />
74396,"Fork Lift, Electric",SU 803647,F30,CATERPILLAR,5/1/2001,CH106,13900,Center for Manufacturing Research,6700<br />
</code><br />
that I could work with. I had text files from previous years on the old system, but they were much weirder to parse. This opened right up in Excel without issue, so I had hope for a much cleaner implementation of my old Python inventory scripts.</p>
<p>Man, I love Python. There&#8217;s <a href="http://docs.python.org/lib/module-csv.html">a built-in CSV module</a> that parses lines into lists, and separates values on each line into separate items in those lists. No parsing required on my part. There&#8217;s a nice third-party module for generating HTML markup (original site is down, but see <a href="http://web.archive.org/web/20070409120933/http://dustman.net/andy/python/HyperText/1.0.1/HyperText-1.0.1.tar.gz">this link from archive.org</a> for the module). And it&#8217;s a mature enough language to where <a href="http://groups.google.com/group/comp.lang.python/msg/42b5a84450278ab2">a years-old Usenet post on sorting</a> is still useful.</p>
<p>On a procedural rather than technical front, I started going around a couple of years ago and snapping pictures of items as I inventoried them. The old camera-phone pictures aren&#8217;t great, but they&#8217;re good enough to identify an item and find its property tag. So now I had a table of inventory data, and a big folder of JPGs named according to the item&#8217;s property tag. So now to convert them into useful web pages:<br />
<span id="more-73"></span></p>
<pre>
import csv
import os
import string
from HyperText.XHTML import TABLE, TR, TH, TD, IMG, DIV, HEAD, STYLE, BR
from HyperText.Documents import Document

notesDict = {
    '064286':'(2007) Surplused',
    }

"""A set of comparer classes."""
## http://groups.google.com/group/comp.lang.python/msg/42b5a84450278ab2

class CmpComposite:
    """Takes a list of compare functions and sorts in that order."""
    def __init__(self,*comparers):
        self.comparers=comparers
    def __call__(self,a,b):
        for cmp in self.comparers:
            c=cmp(a,b)
            if c:
                return c
        return 0

class CmpInverse:
    """Inverses the effect of a cmp."""
    def __init__(self,cmp):
        self.cmp=cmp
    def __call__(self,a,b):
        return -self.cmp(a,b)

class CmpColumn:
    """Sorts on an index of a sequence."""
    def __init__(self,column):
        self.column=column
    def __call__(self,a,b):
        return cmp(a[self.column],b[self.column])

class CmpAttr:
    &#8220;&#8221;"Sorts on an attribute.&#8221;"&#8221;
    def __init__(self, attr):
        self.attr = attr
    def __call__(self, x, y):
        return cmp(getattr(x, self.attr), getattr(y, self.attr))

reader=csv.reader(open(&#8217;inventory_control_listing_13900.csv&#8217;,'rb&#8217;))
equipmentList = []
for row in reader:
    (tag, description, serial, model, manufacturer, arrival,
     location, code, department, cost) = row
    equipmentList.append((tag, description, serial, model, manufacturer,
                          arrival, location, cost))

for sortBy in ['tag', 'location']:
    if sortBy==&#8217;tag&#8217;:
        equipmentList.sort(CmpColumn(0))
    elif sortBy==&#8217;location&#8217;:
        equipmentList.sort(CmpColumn(6))

    outFilename = &#8220;by-%s.html&#8221; % (sortBy)
    print
    print &#8220;Would print to %s&#8221; % (outFile)
    print

    n=0
    seenTags = []
    inventoryTableList=DIV()
    for equipmentItem in equipmentList:
        (itemTag, itemDescription, itemSerial, itemModel, itemManufacturer,
         itemArrival, itemLocation, itemCost) = equipmentItem

        try:
            seenTags.index(itemTag)
        except ValueError:
            seenTags.append(itemTag)
            n=n+1
            if (n%2)==0:
                inventoryTable=TABLE(border=&#8217;1&#8242;,style=&#8217;page-break-after: always&#8217;)
            else:
                inventoryTable=TABLE(border=&#8217;1&#8242;)

            headerRow=TR()
            headerRow.append(TH(&#8217;Tag&#8217;))
            headerRow.append(TH(&#8217;Item Description&#8217;))
            headerRow.append(TH(&#8217;Serial Number&#8217;))
            headerRow.append(TH(&#8217;Model Number&#8217;))
            headerRow.append(TH(&#8217;Arrival&#8217;))
            headerRow.append(TH(&#8217;Manufacturer&#8217;))
            headerRow.append(TH(&#8217;Location&#8217;))
            headerRow.append(TH(&#8217;Cost&#8217;))
            inventoryTable.append(headerRow)

            itemRow=TR()
            if itemTag[0] in string.digits:
                itemTag=&#8221;.join(['0',itemTag])
            itemRow.append(TD(itemTag,align=&#8217;right&#8217;))
            itemRow.append(TD(itemDescription))
            itemRow.append(TD(itemSerial))
            itemRow.append(TD(itemModel))
            itemRow.append(TD(itemArrival,align=&#8217;right&#8217;))
            itemRow.append(TD(itemManufacturer))
            itemRow.append(TD(itemLocation,align=&#8217;center&#8217;))
            itemRow.append(TD(&#8221;$%.2f&#8221; % (float(itemCost)),align=&#8217;right&#8217;))
            inventoryTable.append(itemRow)

            imageRow=TR()
            if os.path.exists(&#8221;%s.jpg&#8221; % itemTag):
                imageRow.append(TD(IMG(src=&#8221;%s.jpg&#8221; % itemTag),
                                   colspan=4,align=&#8217;center&#8217;,width=&#8217;640&#8242;))
            else:
                imageRow.append(TD(IMG(src=&#8221;missing.gif&#8221;),
                                   colspan=4,align=&#8217;center&#8217;,width=&#8217;640&#8242;))

            if os.path.exists(&#8221;%s-detail.jpg&#8221; % itemTag):
                imageRow.append(TD(IMG(src=&#8221;%s-detail.jpg&#8221; % itemTag),
                                   colspan=4,align=&#8217;center&#8217;,width=&#8217;640&#8242;))
            else:
                imageRow.append(TD(IMG(src=&#8221;missing.gif&#8221;),
                                   colspan=4,align=&#8217;center&#8217;,width=&#8217;640&#8242;))

            inventoryTable.append(imageRow)

            notesRow=TR()
            if notesDict.has_key(itemTag):
                notesRow.append(TD(&#8221;Notes: %s&#8221; % notesDict[itemTag],colspan=8))
            else:
                notesRow.append(TD(&#8221;Notes:&#8221;,colspan=8))

            inventoryTable.append(notesRow)

            inventoryTableList.append(inventoryTable)
            inventoryTableList.append(BR())

    head=HEAD()
    head.append(STYLE(type=&#8217;text/css&#8217;))
    htmlOutput=Document(head,inventoryTableList)
    htmlFile=open(outFilename,&#8217;w+&#8217;)
    htmlOutput.writeto(htmlFile)
    htmlFile.close()
</pre>
<p>Once this script has run, I have two web pages of the same inventory data: one sorted by location, and another by property tag. I generally only use the location-sorted one, though. The CSS tries to force page breaks every two items, and judicious use of landscape printing and a bit of scaling in Firefox means I can carry around 40 pages of my report and find everything I need pretty quickly.<br />
<img src='http://blogs.cae.tntech.edu/mwr/files/2008/07/inventory-screenshot.png' alt='Screenshot of Inventory Report in Firefox' /></p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/01/better-inventory-management-through-python/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Wordle: So Much Awesome</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/07/01/wordle-so-much-awesome/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/07/01/wordle-so-much-awesome/#comments</comments>
		<pubDate>Tue, 01 Jul 2008 05:00:43 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/07/01/wordle-so-much-awesome/</guid>
		<description><![CDATA[Wordle generates &#8220;word clouds&#8221; from any random text provided to it. Something like a tag cloud, but for body text rather than categories or tags. The poetry-derived ones are cool, but the one generated from this blog&#8217;s RSS feed is cool, too:

]]></description>
			<content:encoded><![CDATA[<p><a href="http://wordle.net/">Wordle</a> generates &#8220;word clouds&#8221; from any random text provided to it. Something like a tag cloud, but for body text rather than categories or tags. The poetry-derived ones are cool, but the one generated from this blog&#8217;s RSS feed is cool, too:
<div align="center"><a href="http://wordle.net/gallery/wrdl/44617/Untitled" title="Untitled"><img src="http://wordle.net/thumb/wrdl/44617/Untitled" /></a></div>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/07/01/wordle-so-much-awesome/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Transforming Research Computing Practices in TTU&#8217;s College of Engineering</title>
		<link>http://blogs.cae.tntech.edu/mwr/2008/06/29/transforming-research-computing-practices-in-ttus-college-of-engineering/</link>
		<comments>http://blogs.cae.tntech.edu/mwr/2008/06/29/transforming-research-computing-practices-in-ttus-college-of-engineering/#comments</comments>
		<pubDate>Sun, 29 Jun 2008 21:07:51 +0000</pubDate>
		<dc:creator>Mike Renfro</dc:creator>
		
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blogs.cae.tntech.edu/mwr/2008/06/29/transforming-research-computing-practices-in-ttus-college-of-engineering/</guid>
		<description><![CDATA[I have a cunning plan to promote some better (in my opinion) practices for research computing in TTU&#8217;s College of Engineering over the next year or two. This plan is partially derived from things I&#8217;ve already been trying to promote, and some other goals laid out by the Python Software Carpentry folks. I&#8217;d define success [...]]]></description>
			<content:encoded><![CDATA[<p>I have a <a href="http://en.wikipedia.org/wiki/Baldrick">cunning plan</a> to promote some better (in my opinion) practices for research computing in TTU&#8217;s College of Engineering over the next year or two. This plan is partially derived from things I&#8217;ve already been trying to promote, and some other goals laid out by the Python <a href="http://www.swc.scipy.org/">Software Carpentry</a> folks. I&#8217;d define success as remedying any or all of the following potential problems for a given researcher (student or faculty), depending on what type of work they do:
<ol>
<li>By default, the majority of a researcher&#8217;s files are stored on a single drive (hard drive in the office, hard drive at home, or flash drive). A single hardware failure can mean the loss of days, weeks, months, or years of work.</li>
<li>If a researcher makes a mistake on a particular file or set of files that can&#8217;t be easily undone, they may have considerable difficulty in restoring those files to their former state. Most people don&#8217;t have automated backups of any kind, and many just manually save copies of their work onto different folders periodically.</li>
<li>Advisors don&#8217;t have automatic access to their students&#8217; research files upon graduation. Rarely do they have easy access to them during the research period. Collaboration is often reduced to emailing drafts back and forth, or to shuffling materials around on removable media on an <i>ad hoc</i> basis. Simple supervision uses the same methods, but on an even more infrequent basis.</li>
<li>Researchers don&#8217;t have a standard storage location for their research materials that is large enough to contain all relevant files, accessible from around the world, and secured against unauthorized access. GMail doesn&#8217;t count.</li>
<li>Researchers don&#8217;t have a standard place to publish works in progress, completed papers, and anything else that would be of use to the larger research community (at TTU and/or elsewhere).</li>
<li>Researchers don&#8217;t have a facility for others to comment on completed projects, and to collaborate on works in progress.</li>
<li>Researchers with computational needs tend to focus on the types of problems they can solve with PCs in ITS labs, PCs on their desk, or PCs they can purchase on a project budget. These PCs are often underpowered, underutilized, and redundant purchases when you consider multiple projects. They also limit the scope and scale of problems that can be solved.</li>
</ol>
<p>Why would anyone related to TTU&#8217;s Engineering research activities care?
<ol>
<li>We shouldn&#8217;t limit our computational research unnecessarily. We should work to the limit of our available facilities, and use those facilities as effeciently as possible.</li>
<li>Researchers shouldn&#8217;t have to worry about their storage media&#8217;s integrity. They should be able to trust that if they save files somewhere safe, that they&#8217;ll be there the next time they&#8217;re needed. They also shouldn&#8217;t have to always worry about keeping multiple copies organized.</li>
<li>Especially for projects where there is more than one researcher, and also for projects of interest to a supervisor or advisor, the ability to automatically track code and other changes automatically would be great. Even on single-researcher projects, the ability to track all the details of changes means the ability to revert those changes as needed.</li>
<li>Many techniques that are new to a particular research group may be established procedure for another. This could include image processing, Groebner bases, boundary element methods, LaTeX tips, etc. If the various research groups can see the works in progress of other groups, then they at least have an opportunity to comment and suggest alternative strategies. Similarly, if groups are in the habit of constantly publishing their daily successes and failures, then others at TTU or worldwide can avoid reinventing the wheel and/or offer suggestions on how to work around the particular problems.</li>
</ol>
<p>My basic strategy is as follows (some of these have already been done to varying degrees):<span id="more-75"></span>
<ol>
<li>Define types of services that each of the above goals, and set up the specific hardware and software for those services.</li>
<li>Identify faculty and student champions/testers for each of these types of services. Ideally, these researchers would be spread around all departments in Engineering.</li>
<li>Tune service offerings to meet unexpected needs from the testing group.</li>
<li>Branch out to other faculty and student researchers not in the initial testing group. The testing group would provide some credence to the tested methodologies from step 3, and could demonstrate how the business of conducting research had improved as a result.</li>
</ol>
<p>So what does step 1 look like? The following table shows the various services already available in the CAE network, and how they help solve the problems given above:</p>
<div align="center">
<table>
<tbody>
<tr valign="bottom">
<th>Data<br />Safety</th>
<th>Data<br />Recovery</th>
<th>Student File<br />Retention</th>
<th>Universal<br />File Access</th>
<th>Publishing</th>
<th>Collaboration</th>
<th>High-Performance<br />Pooled Computing</th>
<th>Service</th>
</tr>
<tr>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td>File Server</td>
</tr>
<tr>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td align="center"></td>
<td>Web Server</td>
</tr>
<tr>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td>Version Control Server</td>
</tr>
<tr>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td>Software Configuration Management Server</td>
</tr>
<tr>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td>Blog Server</td>
</tr>
<tr>
<td align="center">x</td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td align="center">x</td>
<td align="center">x</td>
<td align="center"></td>
<td>Mailing Lists Server</td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center">x</td>
<td>Cluster Systems</td>
</tr>
</tbody>
</table>
<p></div>
<p>Next up, identifying my champions/testers in different departments, tuning the offerings to meet their unexpected needs, and starting on documentation and publicity materials for the rest of the college.</p>
]]></content:encoded>
			<wfw:commentRss>http://blogs.cae.tntech.edu/mwr/2008/06/29/transforming-research-computing-practices-in-ttus-college-of-engineering/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
