<?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"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>~clay &#187; Systems Management</title>
	<atom:link href="http://daemons.net/~clay/category/work/systems-management-work/feed/" rel="self" type="application/rss+xml" />
	<link>http://daemons.net/~clay</link>
	<description>merely my musings</description>
	<lastBuildDate>Mon, 10 May 2010 17:48:58 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Simulating synchronous programming with Python generators</title>
		<link>http://daemons.net/~clay/2009/05/15/simulating-synchronous-programming-with-python-generators/</link>
		<comments>http://daemons.net/~clay/2009/05/15/simulating-synchronous-programming-with-python-generators/#comments</comments>
		<pubDate>Sat, 16 May 2009 06:41:06 +0000</pubDate>
		<dc:creator>clay</dc:creator>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Geek]]></category>
		<category><![CDATA[Systems Management]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[twisted]]></category>

		<guid isPermaLink="false">http://daemons.net/~clay/?p=287</guid>
		<description><![CDATA[Robey&#8217;s recent article on naggati reminded me of something I&#8217;d been idly pondering for a while. Having recently written an SSH-based host discovery scanner on top of the Twisted asynchronous programming library, I too yearned for a way to write sequences of commands in plain-old imperative code, hiding the callback complexities of event-driven code from [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://robey.lag.net/">Robey</a>&#8217;s recent article on <a href="http://robey.lag.net/2009/03/02/actors-mina-and-naggati.html">naggati</a> reminded me of something I&#8217;d been idly pondering for a while. Having recently written an SSH-based host discovery scanner on top of the <a href="http://twistedmatrix.com/">Twisted</a> asynchronous programming library, I too yearned for a way to write sequences of commands in plain-old imperative code, hiding the callback complexities of event-driven code from users.</p>
<p><a href="http://en.wikipedia.org/wiki/Continuation">Continuations</a> fit the bill nicely. These are functions from which you can return multiple times, resuming right where you left off. With continuations, you could write a sequence of functions that might make asynchronous calls, but the framework would call your continuation back where it left off.</p>
<p>Python does not have first-class continuations, but it does have <a href="http://en.wikipedia.org/wiki/Generator_(computer_science)">generators</a>, and these behave almost identically (for my purposes, at least). A generator is a function that can yield multiple values. Well, actually, it returns an iterator, which then can be used to fetch multiple values from the generator. An example will probably make it clear:</p>
<pre class="brush: python;">
&gt;&gt;&gt; def finite_generator():
...     yield 'apple'
...     yield 'orange'
...     yield 'pear'
...
&gt;&gt;&gt; iterator = finite_generator()
&gt;&gt;&gt; for fruit in iterator:
...     print fruit
...
apple
orange
pear
</pre>
<p>Generators can also run forever:</p>
<pre class="brush: python;">
&gt;&gt;&gt; def infinite_generator():
...     i = 0
...     while True:
...         yield i
...         i += 1
...
&gt;&gt;&gt; iterator = infinite_generator()
&gt;&gt;&gt; for i in iterator:
...     print i
...
0
1
2
3
4
5
... and on and on forever
</pre>
<p>I had been using iterators in my asynchronous host scanner whenever I needed to run asynchronous commands within a loop. The asynchronous programming model prevents you from writing something like:</p>
<pre class="brush: python;">
for foo in bar:
    async_method(foo)
</pre>
<p>Instead, you would do something like this:</p>
<pre class="brush: python;">
def callback(response, iterator):
    do_something_with_response(response)
    schedule_next_task(iterator)

def schedule_next_task(iterator):
    try:
        foo = iterator.next()
        deferred = async_method(foo)
        deferred.addCallback(callback, iterator)
    except StopIteration:
        pass

iterator = iter(bar)
schedule_next_task(iterator)
</pre>
<p>It works like this:</p>
<ol>
<li>We get an iterator for our list, bar &#8212; this could just as well be a generator function</li>
<li>We fetch the first value from the iterator and pass it to the asynchronous method</li>
<li>That method presumably makes some type of I/O request, and responds immediately with a Deferred instance</li>
<li>We add a callback function to the Deferred and request that our iterator instance be passed to it when it is called</li>
<li>Control returns to the event loop, which might be busy scheduling other I/O requests</li>
<li>When the I/O completes, the event loop calls our callback function with the response and our iterator instance</li>
<li>The callback processes the response, and then repeats to step 2, fetching the next item from the iterator</li>
<li>When the iterator is exhausted, the cycle stops</li>
</ol>
<p>It occurred to me that I might be able to extend this concept to use generators as a sort of continuation to emulate synchronous code. What if, instead of returning strings or numbers from a generator, you returned functions? Some wrapper code could initialize the iterator, and then loop over it using the technique above, calling each function returned from the generator.</p>
<p>Tonight I decided to give this a try. Forking off an experimental branch and making a few modifications to the underlying fido host discovery routines, I crafted the following pleisiochronous host scanner:</p>
<pre class="brush: python;">
#!/usr/bin/env python
#
# Use a generator to simulate synchronous execution on an asynchronous framework
#

from fido.common.command import RemoteCommandExecutor
from fido.common.host.unix import UnixHost
from fido.common.ssh import SSHCredentials

from contrib.host.software.sun.host import SolarisHost
from contrib.host.software.linux.host import LinuxHost

from twisted.internet import reactor

import pprint

class PlesiochronousHostScanner(object):
    &quot;&quot;&quot;
    Scans a host over SSH, building a list of host attributes. Built on the Twisted asynchronous
    library, but uses a Python generator function to emulate garden variety synchronous code.
    &quot;&quot;&quot;

    def __init__(self, address, credentials):
        &quot;&quot;&quot;
        address: the IP address to scan
        credentials: a hash like: { 'username': '...' , 'password': '...', 'public_key': '&lt;optional&gt;' }
        &quot;&quot;&quot;

        self.address = address
        self.credentials = credentials
        self.host = UnixHost(RemoteCommandExecutor(address, credentials))
        self.pp = pprint.PrettyPrinter()

        # create some scratch space for the discovery methods
        self.context = { }

        # get an iterator from the generator
        self.iterator = self.scanning_sequence()

    def scanning_sequence(self):
        &quot;&quot;&quot;
        A typical nugget of synchronous code, with one important exception: asynchronous
        functions must be yielded instead of being called directly.
        &quot;&quot;&quot;
        yield self.host.uname

        os = self.context['uname'].split()[0]

        if os == 'SunOS':
            self.host = SolarisHost.from_host(self.host)
            yield self.host.zonename
            yield self.host.zones
        elif os == 'Linux':
            self.host = LinuxHost.from_host(self.host)
        else:
            print &quot;Unable to scan host type: %s&quot; % os
            return

        yield self.host.hostid
        yield self.host.device
        yield self.host.bios
        yield self.host.installed_memory_in_MB
        yield self.host.interfaces

    def callback(self, response):
        self.context.update(response)
        self.schedule_next_task()

    def errback(self, error):
        print &quot;scanning error: %s&quot; % error

    def schedule_next_task(self):
        try:
            function = self.iterator.next()
            deferred = function()
            deferred.addCallbacks(self.callback, self.errback)
        except StopIteration:
            self.scan_complete()

    def start_scan(self):
        self.schedule_next_task()

    def scan_complete(self):
        print &quot;Scan of %s is complete&quot; % self.address
        self.pp.pprint(self.context)

        # In this contrived example, we'll stop the reactor when we've finished scanning a host
        reactor.stop()

if __name__ == '__main__':
    import sys
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option(&quot;-u&quot;, &quot;--username&quot;, dest=&quot;username&quot;)
    parser.add_option(&quot;-p&quot;, &quot;--password&quot;, dest=&quot;password&quot;)

    (options, args) = parser.parse_args()

    address = args.pop(0)
    credentials = iter([SSHCredentials(options.username, options.password, None)])

    scanner = PlesiochronousHostScanner(address, credentials)

    reactor.callWhenRunning(scanner.start_scan)

    reactor.run()
</pre>
<p>It works:</p>
<pre class="brush: plain;">
satellite:~ clay$ python pleisio.py -u username -p password 10.20.30.40
Scan of 10.20.30.40 is complete
{'bios': {'bios_date': '11/15/2007',
          'bios_vendor': 'Sun Microsystems',
          'bios_version': 'S39_3B25'},
 'device': {'system_product': 'Sun Fire X2200 M2',
            'system_serial': '0805QAT0EA',
            'system_uuid': 'bd6529dc-fc79-0010-9e1b-001b245c1d4f',
            'system_vendor': 'Sun Microsystems',
            'system_version': 'Rev 50'},
 'hostid': '0ec2daa6',
 'installed_memory_in_MB': 32768,
 'interfaces': {'bge0': {'ipv4_addresses': [10.20.30.40],
                         'ipv6_addresses': [],
                         'mac_address': 00:1B:24:5C:18:B5,
                         'zone': None},
                'lo0': {'ipv4_addresses': [],
                        'ipv6_addresses': [],
                        'mac_address': None,
                        'zone': None}},
 'uname': 'SunOS myhost.mydomain.com 5.10 Generic_127112-11 i86pc i386 i86pc',
 'zonename': 'global',
 'zones': {'myzone': {'brand': 'native',
                          'ip_mode': 'shared',
                          'root': '/zones/myzone',
                          'state': 'running',
                          'uuid': '09fbf9ba-c0c5-408f-c9e9-820471983f25',
                          'zonename': 'myzone'}}}
</pre>
<p>The beauty of this approach is that the <code>PlesiochronousHostScanner#scanning_sequence</code> method is pretty straightforward, and could actually be written by end users familiar with Python but not familiar with asynchronous programming. It also makes discovery logic much easier to understand than in the state-machine-based asynchronous discovery engine I had previously built.</p>
<p>Having just concocted this tonight, I&#8217;m not sure whether this is something I&#8217;ll pursue, but it has been a fun experiment. I&#8217;m curious what other asynchronous programmers think of this approach.</p>
]]></content:encoded>
			<wfw:commentRss>http://daemons.net/~clay/2009/05/15/simulating-synchronous-programming-with-python-generators/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>setuid() ate my CSS</title>
		<link>http://daemons.net/~clay/2009/05/02/setuid-ate-my-css/</link>
		<comments>http://daemons.net/~clay/2009/05/02/setuid-ate-my-css/#comments</comments>
		<pubDate>Sat, 02 May 2009 10:15:35 +0000</pubDate>
		<dc:creator>clay</dc:creator>
				<category><![CDATA[Engineering]]></category>
		<category><![CDATA[Systems Management]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[setuid]]></category>

		<guid isPermaLink="false">http://daemons.net/~clay/?p=244</guid>
		<description><![CDATA[We ran into an interesting problem while testing a new version of our code deployment tool tonight. By all appearances, the tool was happily deploying code and launching our Java applications, but one of our QA engineers noticed missing CSS on some pages in our test environment. Could that possibly be related to the code deployment tool, which essentially just untars an archive and forks off a little ruby script to start the application?]]></description>
			<content:encoded><![CDATA[<p>We ran into an interesting problem while testing a new version of our code deployment tool tonight. By all appearances, the tool was happily deploying code and launching our Java applications, but one of our QA engineers noticed missing CSS on some pages in our test environment. Could that possibly be related to the code deployment tool, which essentially just untars an archive and forks off a little ruby script to start the application?</p>
<p>Tracing the application&#8217;s system calls with truss revealed that the process was getting EPERM errors while trying to read the CSS files, which live on NFS. One of our more clever engineers decided to start up the application manually, not via the code deployment tool, and found that the CSS loaded just fine when the Java process was invoked directly from the shell. He compared user and group ids, as reported by ps, of JVMs started by our tool and those started manually and found no differences. Hmm.</p>
<p>When looking at the processes&#8217; <code>/proc/&lt;pid&gt;/cred</code> files, however, some differences were apparent. The <code>cred</code> file contains binary data and is best viewed with <code>od</code>:</p>
<p><code><br />
$ od -X /proc/$$/cred<br />
0000000 00002716 00002716 00002716 0000000a<br />
0000020 0000000a 0000000a 00000002 0000000a<br />
0000040 0000000e<br />
0000044<br />
</code></p>
<p>The file consists of a sequence of 32-bit id values in the following order:</p>
<p>* uid<br />
* euid<br />
* suid<br />
* gid<br />
* egid<br />
* sgid<br />
* supplemental group ids &#8230;</p>
<p>You can see how that maps to decimal ids by comparing with <code>id</code> output:</p>
<p><code><br />
$ id -a<br />
uid=10006(clay) gid=10(staff) groups=10(staff),14(sysadmin)<br />
</code></p>
<p>[Solaris geek aside: remember when you wanted to be a member of the sysadmin group so you could run the handy-dandy admintool?]</p>
<p>So what we noticed was that while the manually started JVM and the JVM launched via our code deployment tool had identical uid/euid/sgid and gid/egid/sgid values, they had different supplemental group id lists. Notably, the JVM running under the code deployment tool still had a gid of 0 in its supplemental group list. Letting our Java application servers traipse around the filesystem with elevated privileges is perhaps not the best &#8220;feature&#8221; we&#8217;ve ever implemented.</p>
<p>Trust but verify might be a good foreign policy, but our NFS server wasn&#8217;t having any of it. It thoroughly distrusted the Java app servers claiming to have elevated privileges, and rewarded them with EPERMs for their trouble. Root squash is, after all, a pretty common NFS security measure.</p>
<p>As it turns out, I had implemented a new feature in the code deployment agent to make it switch user id on startup. Previously we handled the user switch by launching the tool under <code>su</code>, but that approach prevented the tool from writing its pid file to the root-owned /var/run directory. The solution, I thought, was just to call <code>setgid()</code> followed by <code>setuid()</code>. We tested that code by verifying the user and group ids with <code>ps</code>, and it seemed to work just great.</p>
<p>Quick: what&#8217;s wrong with this?</p>
<pre class="brush: ruby;">
    def HostUtils.switch_user user
      pwent = Etc::getpwnam(user)
      Process::GID::change_privilege(pwent.gid)
      Process::UID::change_privilege(pwent.uid)
    end
</pre>
<p>Maybe several things, but certainly one thing is that I&#8217;ve completely neglected supplemental group ids. I should have written:</p>
<pre class="brush: ruby;">
    def HostUtils.switch_user user
      pwent = Etc::getpwnam(user)
      Process::initgroups(user, pwent.gid)
      Process::GID::change_privilege(pwent.gid)
      Process::UID::change_privilege(pwent.uid)
    end
</pre>
<p>That call to <a href="http://www.ruby-doc.org/core/classes/Process.html#M003208">Process::initgroups</a> makes all the difference. After making the change, the apps could access NFS and our test site looked all pretty again. Good thing we caught it when we did!</p>
<p>Turns out this is a fairly <a href="http://www.ruby-forum.com/topic/110492">common problem</a>, and I feel especially dumb for overlooking something so obvious. Live and learn.</p>
]]></content:encoded>
			<wfw:commentRss>http://daemons.net/~clay/2009/05/02/setuid-ate-my-css/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
